2 条题解
-
0
感觉
$$f_{i,j}>f_{j,i} \\ \Leftrightarrow h_is_j+p_ih_j+s_ip_j>h_ip_j+p_is_j+s_ih_j \\ \Leftrightarrow h_ip_j+p_is_j+s_ih_j-(h_is_j+p_ih_j+s_ip_j)<0 \\ \Leftrightarrow 2h_ip_j+2p_is_j+2s_ih_j-2h_is_j-2p_ih_j-2s_ip_j+h_ih_j-h_ih_j+p_ip_j-p_ip_j<0 \\ \Leftrightarrow (h_ih_j+h_ip_j-2h_is_j-p_ih_j-p_ip_j+2p_is_j)-(h_ih_j-h_ip_j+p_ih_j-p_ip_j-2s_ih_j+2s_ip_j)<0 \\ (h_i-p_i)(h_j+p_j-2s_j)-(h_i+p_i-2s_i)(h_j-p_j)<0 \\$$至少我的做法是好题。
先推式子,令 表示 胜过 的概率则有 $f_{i,j}=\dfrac{h_i}{h_i+p_i+s_i}\dfrac{s_j}{h_j+p_j+s_j}+\dfrac{p_i}{h_i+p_i+s_i}\dfrac{h_j}{h_j+p_j+s_j}+\dfrac{s_i}{h_i+p_i+s_i}\dfrac{p_j}{h_j+p_j+s_j}$。
所以 平均赢 当且仅当 。
你发现这一点也不好维护,考虑继续推式子,尝试把 中的 拆开。
我们把 和 同乘 ,则有容易注意到式子变成了两个分别只与 有关的向量的叉积 。
题目现在转化为了:给出 个向量,你需要找出有多少个三元组,使得三个向量依次和前一个向量叉乘 。
这也就是有多少个三元组满足不存在一条直线使得三个向量均在直线的同一侧(不包括直线上)。
简单容斥以下,真难则反,考虑计数不合法向量个数。
我们考虑计数经典套路。
给每个无序三元组钦定一个代表元。
这里我们钦定是以逆时针角度 的那个角的终边为起点,逆时针按顺序写下的有序三元组为代表元。
容易发现这个代表元在存在平角时可能有重复,所以先不算有平角的方案。
那么极角排序一下,双指针一下以当前向量为起点,终点最远在哪里,然后中间随便选两个都是不合法的。
至于有平角的方案,注意到直接算会在有两个同向向量且另一个向量反向时算重,这个是好计数的,减掉即可。
然后就做完了,注意实现细节。
:::info[代码]#include<bits/stdc++.h> #define int long long using namespace std; struct cow { __int128_t h,p,s; friend __int128_t cross(cow u,cow v) {return((u.h-u.p)*(v.h+v.p-2*v.s))-((v.h-v.p)*(u.h+u.p-2*u.s));} int get()const { if(h+p==2*s) return h<p; return h+p-2*s<0; } bool operator<(const cow u)const { if(get()!=u.get()) return get()<u.get(); return cross(*this,u)>0; } bool operator!=(const cow u)const {return (*this<u)||(u<*this);} bool operator==(const cow u)const {return !(*this!=u);} }; int T,n; vector<cow>vec,tmp; signed main() { cin>>T; while(T--) { cin>>n; vec.clear(); tmp.clear(); int cnt=0; for(int i=0;i<n;i++) { int h,p,s; cin>>h>>p>>s; if(h==p&&p==s) ++cnt; else vec.push_back({h,p,s}); } n-=cnt; if(n==0) { cout<<"0\n"; continue; } sort(vec.begin(),vec.end()); tmp=vec; for(auto x:tmp) vec.push_back(x); int r=0,R=0,tr=0,ans=0,tot=0; cnt=0; for(int i=0;i<n;i++) { r=max(r,i); R=max(R,i); while(r+1<n&&vec[i]==vec[r+1]) ++r; while(R+1<n&&vec[i]==vec[R+1]) ++R; while(r+1<n+i&&cross(vec[i],vec[r+1])>=0&&vec[i]!=vec[r+1]) ++r; while(R+1<n+i&&cross(vec[i],vec[R+1])>0) ++R; ans+=(R-i)*(R-i-1)/2; tot+=(r-R)*(r-R-1)/2; cnt+=r-R; } if(vec[0]==vec[n-1]) cout<<"0\n"; else cout<<n*(n-1)*(n-2)/6-ans-cnt*(n-2)/2+tot<<"\n"; } }:::
-
0
Analysis by Bing-Dong Liu
Cow beats cow on average when $h_i s_j + p_i h_j + s_i p_j > h_j s_i + p_j h_i + s_j p_i$. The problem asks us to compute the number of "good" triples such that cow beats cow , cow beats cow , and cow beats cow . We define a "bad" triple as any triple that is not good.
Subtask 1 ()
It suffices to brute force all triples of cows.
Subtask 2 (, the sum of over all tests does not exceed )
First, consider the case where there are no ties (i.e., for every pair of cows, one beats the other). Let's use complementary counting and count the number of bad triples. Consider a single triple . In a good triple, each cow wins exactly once against the others in the triple. In a bad triple, this balance is broken; one cow must beat the other two (2 wins), one cow wins once, and one cow wins zero times.
If we let represent the number of cows among the other cows that cow beats, the number of pairs that form a bad triple with (where is the 2-win cow) is . The answer to the problem is .
For the full subtask solution, we need to handle ties. Note that if and , that cow draws with all other cows, so we can ignore such points. When cows and tie, we have
$$h_i s_j + p_i h_j + s_i p_j = h_j s_i + p_j h_i + s_j p_i$$ $$\frac{h_i - s_i}{p_i - s_i} = \frac{h_j - s_j}{p_j - s_j}$$This means that tying is transitive: if cow ties with cows and , then cow ties with cow . We can first merge cows that tie into components and then use knapsack-like DP to find the total count of valid non-tying triples. Afterward, we subtract from this count, just as in the no-tie case. Finally, note that triples where cow beats cows and , but cows and tie with each other, were not included in the initial "no-tie" triple count but were subtracted in the second step. We must add all such triples back to the answer.
The time complexity is to check the winner between each pair of cows.
Ben's code:
#include <bits/stdc++.h> using namespace std; struct DSU { vector<int> e; void init(int N) { e.assign(N, -1); } int get(int x) { return e[x] < 0 ? x : e[x] = get(e[x]); } int size(int x) { return -e[get(x)]; } bool unite(int x, int y) { x = get(x), y = get(y); if (x == y) return false; if (e[x] > e[y]) swap(x, y); e[x] += e[y], e[y] = x; return true; } }; bool beat(const array<int, 3>& a, const array<int, 3>& b) { long long sum = 0; for (int i = 0; i < 3; ++i) { sum += (long long)a[i] * b[(i + 1) % 3]; sum -= (long long)b[i] * a[(i + 1) % 3]; } return sum > 0; } long long c2(long long c) { return c * (c - 1) / 2; } void solve() { int N; cin >> N; vector<array<int, 3>> points; for (int i = 0; i < N; ++i) { array<int, 3> a; cin >> a[0] >> a[1] >> a[2]; if (a[0] == a[1] && a[0] == a[2]) continue; points.push_back(a); } N = points.size(); vector<vector<bool>> beats(N, vector<bool>(N)); for (int i = 0; i < N; ++i) for (int j = 0; j < N; ++j) beats[i][j] = beat(points[i], points[j]); DSU D; D.init(N); for (int i = 0; i < N; ++i) for (int j = i + 1; j < N; ++j) if (!beats[i][j] && !beats[j][i]) D.unite(i, j); array<long long, 4> ways_k = {1, 0, 0, 0}; for (int i = 0; i < N; ++i) if (D.get(i) == i) for (int j = 2; j >= 0; --j) ways_k[j + 1] += ways_k[j] * D.size(i); long long ans = ways_k[3]; for (int i = 0; i < N; ++i) { vector<int> with_repr(N, 0); int c = 0; for (int j = 0; j < N; ++j) { if (beats[i][j]) { ++c; ans += with_repr[D.get(j)]; ++with_repr[D.get(j)]; } } ans -= c2(c); } cout << ans << "\n"; } int main() { cin.tie(0)->sync_with_stdio(0); int TC; cin >> TC; while (TC--) solve(); return 0; }Subtask 3 ()
As stated above, cow beats cow on average when $h_i s_j + p_i h_j + s_i p_j > h_j s_i + p_j h_i + s_j p_i$. Equivalently, this can be rewritten as $h_i(s_j - p_j) + p_i(h_j - s_j) + s_i(p_j - h_j) > 0$. Note that increasing , and by a fixed constant does not change the inequality. Similarly, we can show the same property for , and . Using this observation, the tuple can be reduced to . This reduction allows us to work in 2 dimensions.
Let and . Now, cow beats cow on average when . Geometrically, this is equivalent to the cross product: occurs when vector is counter-clockwise relative to vector . That is, if is the angle of vector , then the condition is equivalent to .
We can sort the remaining vectors by angle. Now, a bad triple occurs when the three vectors lie within a 180-degree range, and a good triple happens otherwise (i.e., the origin is strictly inside the triangle formed by the vectors). To get the final answer, it suffices to perform further computation.
To count the number of bad triples, we can optimize the Subtask 2 solution. We can compute all in linear time using a two-pointers approach to count the number of such that .
To count the number of good triples directly, let's consider a single cow . The vector splits the 2D plane into two half-planes. A good triple consists of and two other cows and such that the vectors span more than 180 degrees. If we compute
pos_over_halfrepresenting the index of the number of such that , then for a fixed pair where beats , the number of valid 's ispos_over_half[j] - pos_over_half[i]. To compute the sum over all , we can apply a layer of prefix sums.Both implementations need to handle edge cases where cows tie. The time complexity is due to sorting.
Ben's code (complementary counting):
#include <bits/stdc++.h> using namespace std; struct Point { long long x, y; }; long long cross(Point a, Point b) { return a.x * b.y - a.y * b.x; } int half(Point p) { return p.y > 0 || (p.y == 0 && p.x > 0) ? 1 : -1; } bool angleCmp(Point a, Point b) { int h1 = half(a), h2 = half(b); return h1 == h2 ? cross(a, b) > 0 : h1 < h2; } long long c3(long long n) { return n * (n - 1) * (n - 2) / 6; } long long c2(long long n) { return n * (n - 1) / 2; } void solve() { int N; cin >> N; vector<Point> points; for (int i = 0; i < N; ++i) { int r, p, s; cin >> r >> p >> s; p -= s, r -= s; if (p || r) points.push_back({(long long)p, (long long)r}); } sort(points.begin(), points.end(), angleCmp); vector<pair<Point, long long>> points2; for (const auto& t : points) { if (!points2.empty() && !angleCmp(points2.back().first, t) && !angleCmp(t, points2.back().first)) { points2.back().second++; } else { points2.push_back({t, 1}); } } long long ans = c3(points.size()); int r = -1; long long sum = 0; int M = points2.size(); for (int l = 0; l < M; ++l) { if (r < l) { r = l; sum = points2[l].second; } else { sum -= points2[l].second; } while (cross(points2[l].first, points2[(r + 1) % M].first) > 0) { ++r; sum += points2[r % M].second; } ans -= (c3(sum + points2[l].second) - c3(sum)); if ((r + 1) % M != l && cross(points2[l].first, points2[(r + 1) % M].first) == 0) { ans -= c2(points2[l].second) * points2[(r + 1) % M].second; ans -= points2[l].second * sum * points2[(r + 1) % M].second; } } cout << ans << "\n"; } int main() { cin.tie(0)->sync_with_stdio(0); int TC; cin >> TC; while (TC--) solve(); return 0; }My code (direct counting):
#include <bits/stdc++.h> using namespace std; struct point { int x, y; bool half() { return y < 0 || (y == 0 && x < 0); } }; long long cross(point a, point b) { return 1ll * a.x * b.y - 1ll * a.y * b.x; } bool equals(point a, point b) { return a.half() == b.half() && !cross(a, b); } int main() { cin.tie(0)->sync_with_stdio(0); int t; cin >> t; while (t--) { int n; cin >> n; vector<point> v; for (int i = 0; i < n; i++) { int x, y, z; cin >> x >> y >> z; x -= z, y -= z; if (x || y) v.push_back({x, y}); } n = v.size(); sort(v.begin(), v.end(), [&](point a, point b) { if (a.half() != b.half()) return a.half() < b.half(); return cross(a, b) > 0; }); vector<int> nxt_diff(n * 2); for (int i = n * 2 - 1; i >= 0; i--) nxt_diff[i + 1] = i < n * 2 - 1 && equals(v[i % n], v[(i + 1) % n]) ? nxt_diff[i + 1] : i + 1; vector<int> pos_over_half(n * 2); for (int i = 0, j = 0; i < n * 2; i++) { j = max(j, nxt_diff[i]); while (j < i + n && cross(v[i % n], v[j % n]) >= 0) j++; pos_over_half[i] = j; } vector<int> pos_bad(n * 2); for (int i = 0, j = 0; i < n * 2; i++) { j = max(j, nxt_diff[i]); while (cross(v[i % n], v[j % n]) > 0) j++; pos_bad[i] = j; } vector<long long> sum_pos_bad(n * 2 + 1); for (int i = 0; i < n * 2; i++) sum_pos_bad[i + 1] = sum_pos_bad[i] + pos_bad[i]; long long ans = 0; for (int i = 0, j = 0; i < n; i++) { j = max(j, nxt_diff[i]); while (j < pos_bad[i] && pos_over_half[j] < pos_over_half[i]) j++; if (j < pos_bad[i]) { ans -= sum_pos_bad[pos_bad[i]] - sum_pos_bad[j]; ans -= 1ll * pos_over_half[i] * (pos_bad[i] - j); } } assert(ans % 3 == 0); ans /= 3; cout << ans << '\n'; } }
- 1
信息
- ID
- 2252
- 时间
- 2000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 3
- 已通过
- 1
- 上传者