题解 P8398 [CCC2022 S4] Good Triplets

发布时间 2023-07-17 21:23:27作者: HQJ2007

显然,答案不好直接求,我们考虑用总数减去不合法的方案数。

为了不算重,我们每次只考虑当前点与圆心连线交圆周于一点所形成的半圆内的不合法情况,然后用组合数算出剩下两个点的选择方案数。

我们可以用前缀和记录区间内点的数量,然后对于一个点可以算出它的对称点,两段区间拼起来就可以算出半圆内的点数。

最后还要减去退化的三角形,一种是三个点在同一位置,另一种是有两个点在同一位置。

值得注意的是,如果周长为奇数,对称点以及半圆的划分会略有不同,需要特判一下。

复杂度线性。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6 + 5;
ll n, c, a[N], sum[N], ans, cnt[N];
ll getnum(ll w, ll w2) { //计算区间内点的数量 
    ll num;
    if (w2 > w) num = sum[c - 1] - sum[w2 - 1] + sum[w];
    else {
        if (w2 > 0) num = sum[w] - sum[w2 - 1];
        else num = sum[w];
    }
    return num;
}
int main() {
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    cin >> n >> c;
    ans = n * (n - 1) * (n - 2) / 6; //总数 
    for (int i = 1; i <= n; ++i) cin >> a[i], ++sum[a[i]], ++cnt[a[i]];
    for (int i = 1; i < c; ++i) sum[i] += sum[i - 1];
    for (int i = 1; i <= n; ++i) {
        ll w = a[i], w2, num;
        w2 = (w + c / 2) % c; //对称点 
        if (c % 2) w2 = (w2 + 1) % c;
        num = getnum(w, w2);
        num -= cnt[w];
        ans -= num * (num - 1) / 2;
    }
    for (int i = 0; i < c; ++i) { //减去退化的三角形 
        if (cnt[i] >= 3) ans -= cnt[i] * (cnt[i] - 1) * (cnt[i] - 2) / 6; //三点重合 
        if (cnt[i] >= 2) { //两点重合 
            ll w = i, w2 = (i + c / 2) % c, num;
            if (c % 2) w2 = (w2 + 1) % c;
            num = getnum(w, w2);
            if (c % 2 == 0) {
                num -= cnt[w] + cnt[w2];
                ans -= cnt[i] * (cnt[i] - 1) / 2 * num;
            } else {
                num -= cnt[w];
                ans -= cnt[i] * (cnt[i] - 1) / 2 * num;
            }
        }
    }
    cout << ans << endl;
    return 0;
}