HDU 4609 3-idiots

HDU 4609 3-idiots FFT


传送门: http://acm.hdu.edu.cn/showproblem.php?pid=4609

题意

给n条边,问任取三条边是否能构成三角形的概率。

思路

首 先 把 题 意 转 化 一 下 , 即 找 下 列 式 子 的 个 数 : 首先把题意转化一下,即找下列式子的个数:
∑ i = 1 n ∑ j = 1 n ∑ k = 1 n a i + a j > a k C n 3      ( i ≠ j ≠ k 且 a i 和 a j 是 较 小 的 两 条 边 ) \frac{\sum_{i=1}^n\sum_{j=1}^n\sum_{k=1}^na_i+a_j>a_k}{C_n^3}\;\;(i\ne j\ne k且a_i和a_j是较小的两条边) Cn3i=1nj=1nk=1nai+aj>ak(i=j=kaiaj)

看 到 三 角 形 , 首 先 想 到 就 是 把 所 有 的 a i + a j 的 个 数 存 在 一 个 数 组 里 , 然 后 再 逐 一 判 断 每 一 个 a k 是 否 符 合 。 看到三角形,首先想到就是把所有的a_i+a_j的个数存在一个数组里,然后再逐一判断每一个a_k是否符合。 ai+ajak

但 是 , O ( n 2 ) 的 复 杂 度 是 会 T 的 , 但 是 O ( n l o g n ) 的 复 杂 度 不 会 T , 那 么 就 需 要 用 到 F F T 的 知 识 了 。 \red{但是,O(n^2)的复杂度是会T的,但是O(nlogn)的复杂度不会T,那么就需要用到FFT的知识了。} O(n2)TO(nlogn)TFFT

F F T 是 两 个 多 项 式 相 乘 , 但 是 上 面 是 要 让 我 们 两 个 相 加 , 没 关 系 , 我 们 让 a i 为 x 的 指 数 , a i 出 现 的 次 数 为 该 x 的 系 数 , 用 一 个 c n t 数 组 保 存 , 即 : FFT是两个多项式相乘,但是上面是要让我们两个相加,没关系,我们让a_i为x的指数,a_i出现的次数为该x的系数,用一个cnt数组保存,即: FFTaixaixcnt

A ( x ) = c n t [ a [ 0 ] ] x a [ 0 ] + c n t [ a [ 1 ] ] x a [ 1 ] + . . . + c n t [ a [ n − 1 ] ] x a [ n − 1 ] A(x)=cnt[a[0]]x^{a[0]}+cnt[a[1]]x^{a[1]}+...+cnt[a[n - 1]]x^{a[n-1]} A(x)=cnt[a[0]]xa[0]+cnt[a[1]]xa[1]+...+cnt[a[n1]]xa[n1]

所 以 我 们 让 A ( x ) × A ( x ) , 就 可 以 得 到 任 意 两 条 边 之 和 , 并 且 得 到 该 和 的 个 数 , 并 存 在 n u m 数 组 里 。 当 然 , 这 里 会 出 现 很 多 问 题 。 所以我们让A(x)\times A(x),就可以得到任意两条边之和,并且得到该和的个数,并存在num数组里。当然,这里会出现很多问题。 A(x)×A(x)num

1 、 i ≠ j , 所 以 要 让 a i + a i 的 个 数 减 1 , 即 n u m [ a [ i ] + a [ i ] ] − − ; 1、i\ne j,所以要让a_i+a_i的个数减1,即num[a[i]+a[i]]--; 1i=jai+ai1,num[a[i]+a[i]];
2 、 先 取 a 1 后 取 a 3 和 先 取 a 3 后 取 a 1 是 一 样 的 , 所 以 我 们 需 要 让 每 一 个 和 都 要 除 2 , 即 n u m [ i ] / = 2 ; 2、先取a_1后取a_3和先取a_3后取a_1是一样的,所以我们需要让每一个和都要除2,即num[i]/=2; 2a1a3a3a12num[i]/=2;

然 后 我 们 用 一 个 s u m 数 组 存 下 n u m 数 组 的 前 缀 和 , 然 后 遍 历 每 条 边 , 取 两 条 边 之 和 比 该 条 边 大 的 个 数 , 即 a n s + = s u m [ a [ n − 1 ] ∗ 2 ] − s u m [ a [ i ] ] , 前 面 的 a [ n − 1 ] ∗ 2 是 前 缀 和 最 大 的 地 方 。 然后我们用一个sum数组存下num数组的前缀和,然后遍历每条边,取两条边之和比该条边大的个数,即ans+=sum[a[n - 1]*2]-sum[a[i]],前面的a[n-1]*2是前缀和最大的地方。 sumnumans+=sum[a[n1]2]sum[a[i]]a[n1]2

但 是 , 还 是 有 很 多 问 题 ! ! ! 在 刚 开 始 的 时 候 说 过 , 两 条 边 之 和 必 须 是 两 条 较 小 的 边 , 所 以 还 要 处 理 下 面 几 种 情 况 : \red{但是,还是有很多问题!!!}在刚开始的时候说过,两条边之和必须是两条较小的边,所以还要处理下面几种情况:

1 、 两 条 边 都 大 于 a [ i ] , 即 a n s − = ( n − i − 1 ) ∗ ( n − i − 1 ) / 2 ; 1、两条边都大于a[i],即ans-=(n-i-1)*(n-i-1)/2; 1a[i]ans=(ni1)(ni1)/2;
2 、 一 条 边 大 于 a [ i ] , 一 条 边 小 于 a [ i ] , 即 a n s − = ( n − i − 1 ) ∗ i ; 2、一条边大于a[i],一条边小于a[i],即ans-=(n-i-1)*i; 2a[i]a[i]ans=(ni1)i;
3 、 两 条 边 中 有 一 条 边 等 于 a [ i ] , 即 a n s − = ( n − 1 ) ; 3、两条边中有一条边等于a[i],即ans-=(n-1); 3a[i],ans=(n1);

最 后 的 a n s 就 是 满 足 条 件 的 所 有 个 数 , 然 后 计 算 C n 3 , 最 后 算 概 率 , 保 留 7 位 小 数 。 最后的ans就是满足条件的所有个数,然后计算C_n^3,最后算概率,保留7位小数。 ansCn37

但 是 , 博 主 t s b 了 ! ! ! 明 知 道 n 有 1 e 5 , 然 后 计 算 C n 3 会 爆 i n t , 所 以 我 还 用 一 个 l o n g l o n g 的 变 量 保 存 下 来 , 即 t = n ∗ ( n − 1 ) ∗ ( n − 2 ) / 6 , 是 不 是 有 一 点 问 题 , 那 就 是 要 在 n 之 前 加 一 个 ( l o n g l o n g ) 强 制 转 换 一 下 , 我 不 过 在 这 个 地 方 错 了 10 多 发 吧 。 。 。 \red{但是,博主tsb了!!!}明知道n有1e5,然后计算C_n^3会爆int,所以我还用一个longlong的变量保存下来,即t=n*(n-1)*(n-2)/6,是不是有一点问题,那就是要在n之前加一个\red{(longlong)}强制转换一下,我不过在这个地方错了10多发吧。。。 tsbn1e5Cn3intlonglongt=n(n1)(n2)/6n(longlong)10

Code(826MS)


#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef long double ld;
typedef pair<int, int> pdd;

#define INF 0x3f3f3f3f
#define lowbit(x) x & (-x)
#define mem(a, b) memset(a , b , sizeof(a))
#define FOR(i, x, n) for(int i = x;i <= n; i++)

// const ll mod = 998244353;
// const ll mod = 1e9 + 7;
// const double eps = 1e-6;
const double PI = acos(-1);

const int N = 4e5 + 10;

struct Complex {
    double r, i;
    Complex() {r == 0; i = 0;};
    Complex(double real, double imag) : r(real), i(imag) {};
}A[N], B[N];

Complex operator + (Complex a, Complex b) {
    return Complex(a.r + b.r, a.i + b.i);
}

Complex operator - (Complex a, Complex b) {
    return Complex(a.r - b.r, a.i - b.i);
}

Complex operator * (Complex a, Complex b) {
    return Complex(a.r * b.r - a.i * b.i, a.r * b.i + a.i * b.r);
}

int rev[N], len, lim = 1;

void FFT(Complex *a, int opt) {
    for(int i = 0;i < lim; i++) {
        if(i < rev[i])
            swap(a[i], a[rev[i]]);
    }
    for(int dep = 1;dep <= log2(lim); dep++) {
        int m = 1 << dep;
        Complex wn = Complex(cos(2.0 * PI / m), opt * sin(2.0 * PI / m));
        for(int k = 0;k < lim; k += m) {
            Complex w = Complex(1, 0);
            for(int j = 0;j < m / 2; j++) {
                Complex t = w * a[k + j + m / 2];
                Complex u = a[k + j];
                a[k + j] = u + t;
                a[k + j + m / 2] = u - t;
                w = w * wn;
            }
        }
    }
    if(opt == -1) { // 逆变换
        for(int i = 0;i < lim; i++) a[i].r /= lim;
    }
}

int getBit(int n) {
    while(lim < (n << 1)) {
        lim <<= 1;
        len++;
    }
    for(int i = 0;i < lim; i++) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (len - 1));
}

ll a[N], cnt[N];
ll sum[N], num[N];

void solve() {
    int T;
    scanf("%d",&T);
    while(T--) {
        mem(rev, 0);
        mem(A, 0);
        mem(num, 0);
        mem(cnt, 0);
        int n;
        len = 0, lim = 1;
        scanf("%d",&n);
        for(int i = 0;i < n; i++) {
            scanf("%lld",&a[i]); // 把a[i]看成x的指数,使乘法变加法
            cnt[a[i]]++; // 把a[i]看成每个x^{a_i}的系数,然后再FFT
        }
        sort(a, a + n);
        getBit(a[n - 1] + 1);
        int Max = a[n - 1] * 2;
        for(int i = 0;i <= a[n - 1]; i++) {
            A[i].r = cnt[i];
        }
        FFT(A, 1);
        for(int i = 0;i < lim; i++) {
            A[i] = A[i] * A[i];
        }
        FFT(A, -1);
        for(int i = 0;i < lim; i++) {
            num[i] = A[i].r = (ll)(A[i].r + 0.5); // FFT之后每个a_i+a_j的系数存在num里
        }
        for(int i = 0;i < n; i++) {
            num[a[i] + a[i]]--; // 把i=j的部分减掉
        }
        sum[0] = 0;
        for(int i = 0;i <= Max; i++) {
            num[i] /= 2; // 重复取,除2即可
            sum[i] = sum[i - 1] + num[i]; // 取前缀和,最后判断的时候方便一点
        }
        ll ans = 0;
        for(int i = 0;i < n; i++) {
            ans += sum[Max] - sum[a[i]]; // 统计大于a[i]
        }
        for(int i = 0;i < n; i++) { // 处理符合两边大于a_k但是不正确的个数(因为判断是否为三角形必须是两条较小的边大于最长边)
            ans -= ll(n - i - 1) * (n - i - 2) / 2; // 两边都大于a[i]
            ans -= ll(n - i - 1) * i; // 一边大于a[i],一边小于a[i]
            ans -= ll(n - 1); // 两边边中有一条边等于a[i]
        }
        ll t = 1ll * n * (n - 1) * (n - 2) / 6;

        printf("%.7lf\n",(double)ans / t);
    }
}

signed main() {
    ios_base::sync_with_stdio(false);
    //cin.tie(nullptr);
    //cout.tie(nullptr);
#ifdef FZT_ACM_LOCAL
    freopen("in.txt", "r", stdin);
    freopen("out.txt", "w", stdout);
    signed test_index_for_debug = 1;
    char acm_local_for_debug = 0;
    do {
        if (acm_local_for_debug == '$') exit(0);
        if (test_index_for_debug > 20)
            throw runtime_error("Check the stdin!!!");
        auto start_clock_for_debug = clock();
        solve();
        auto end_clock_for_debug = clock();
        cout << "Test " << test_index_for_debug << " successful" << endl;
        cerr << "Test " << test_index_for_debug++ << " Run Time: "
             << double(end_clock_for_debug - start_clock_for_debug) / CLOCKS_PER_SEC << "s" << endl;
        cout << "--------------------------------------------------" << endl;
    } while (cin >> acm_local_for_debug && cin.putback(acm_local_for_debug));
#else
    solve();
#endif
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值