HDU 4609 3-idiots(FFT)

该博客讨论了一个数学问题,涉及随机选取三条边形成三角形的概率。通过给出的输入样例和解析,作者展示了如何计算给定长度的树枝能组成三角形的概率,并提供了相应的C++代码实现。文章最后提到了感谢某人的思路协助。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原题题面

King OMeGa catched three men who had been streaking in the street. Looking as idiots though, the three men insisted that it was a kind of performance art, and begged the king to free them. Out of hatred to the real idiots, the king wanted to check if they were lying. The three men were sent to the king’s forest, and each of them was asked to pick a branch one after another. If the three branches they bring back can form a triangle, their math ability would save them. Otherwise, they would be sent into jail.
However, the three men were exactly idiots, and what they would do is only to pick the branches randomly. Certainly, they couldn’t pick the same branch - but the one with the same length as another is available. Given the lengths of all branches in the forest, determine the probability that they would be saved.

输入格式

An integer T(T≤100)T(T≤100)T(T100) will exist in the first line of input, indicating the number of test cases.
Each test case begins with the number of branches N(3≤N≤105)N(3≤N≤10^5)N(3N105).
The following line contains NNN integers ai(1≤ai≤105)a_i (1≤a_i≤10^5)ai(1ai105), which denotes the length of each branch, respectively.

输出格式

Output the probability that their branches can form a triangle, in accuracy of 7 decimal places.

输入样例

2
4
1 3 3 4
4
2 3 3 4

输出样例

0.5000000
1.0000000

题面分析

给定n个点,随机取3条边,求能组成三角形的概率。
答案很简单,就是∑i=1n∑j=1n∑k=1n[ai+aj>ak](i≠j≠k)Cn3\frac{\sum_{i=1}^{n}{\sum_{j=1}^{n}{\sum_{k=1}^{n}[a_i+a_j>a_k](i\neq j\neq k)}}}{C_{n}^{3}}Cn3i=1nj=1nk=1n[ai+aj>ak](i=j=k)
首先我们要计算出任意两条边两两相加的结果,即∑i=1n∑j=1n(ai+aj)(i≠j)\sum_{i=1}^{n}{\sum_{j=1}^{n}{(a_i+a_j)(i\neq j)}}i=1nj=1n(ai+aj)(i=j)
只需要对aaa做卷积,然后减去i=ji=ji=j的情况后除以2(取两条边不分先后)。
于是我们只要统计所有的ai+aj<=aka_i+a_j<=a_kai+aj<=ak的个数再从1里减掉即可。

#include<bits/stdc++.h>
using namespace std;
const int maxn=4e5;
#define cp complex<double>
#define ll long long
cp a[maxn+10];
const double pi=acos(-1.0);
ll rev[maxn+10];
int getBit(int n)
{
    int len=1;
    while(len<(n<<1)) len<<=1;
    for(int i=0; i<len; i++) rev[i]=(rev[i>>1]>>1)|(i&1 ? len>>1 : 0);
    return len;
}
void FFT(cp *a, int n, int flag)
//inv是1时是系数转点值,-1是点值转系数
{
    for(int i=0; i<n; ++i)
    {
        if (i<rev[i]) swap(a[i], a[rev[i]]);
    }
    for(int mid=1; mid<n; mid*=2)
    {
        cp w(cos(pi*1.0/mid), flag*sin(pi*1.0/mid));//单位根
        for(int i=0; i<n; i+=mid*2)
        {
            cp omega(1, 0);
            for(int j=0; j<mid; j++, omega*=w)
            {
                cp x=a[i+j];
                cp y=omega*a[i+j+mid];
                a[i+j]=x+y;
                a[i+j+mid]=x-y;
            }
        }
    }
}
ll cnt[maxn+10];
ll b[maxn+10];
ll prefix[maxn+10];
ll num[maxn+10];
void solve()
{
    int t;
    scanf("%d", &t);
    while(t--)
    {
        int n;
        scanf("%d", &n);
        memset(cnt, 0, sizeof(cnt));
        for(int i=0; i<n; ++i)
        {
            scanf("%lld", &b[i]);
            cnt[b[i]]++;
        }
        sort(b, b+n);
        ll limit=getBit(b[n-1]+1);//数组是0~b[n-1]
        for(int i=0; i<limit; ++i)
        {
            a[i].real(i<=b[n-1] ? cnt[i] : 0);
            a[i].imag(0);
//            printf("%.f ", a[i].real());
        }
//        printf("\n");
        FFT(a, limit, 1);
        for(int i=0; i<limit; ++i)
        {
            a[i]*=a[i];
        }
        FFT(a, limit, -1);
        for(int i=0; i<limit; ++i)
        {
            num[i]=(ll)(a[i].real()/limit+0.5);
        }
        for(int i=0; i<n; ++i)
        {
            num[b[i]+b[i]]--;
            //把自己的和自己的去掉
        }
        prefix[0]=0;
        for(int i=1; i<=b[n-1]*2; ++i)//(0~2*b[n-1])
        {
            num[i]/=2;
            prefix[i]=prefix[i-1]+num[i];//A取B和B取A是同一种,取前缀和
        }
        ll ans=0;
        for(int i=0; i<n; ++i)
        {
            ans+=prefix[b[i]];//统计两条之和小于b[i]的
        }
        ll total=(ll) n*(n-1)*(n-2)/6;
//        printf("%lld %lld\n", ans, total);
        printf("%.7lf\n", 1.0-(double) ans/total);
    }
}
signed main()
{
//    ios_base::sync_with_stdio(false);
//    cin.tie(0);
//    cout.tie(0);
#ifdef ACM_LOCAL
    freopen("in.txt", "r", stdin);
    freopen("out.txt", "w", stdout);
    long long test_index_for_debug=1;
    char acm_local_for_debug;
    while(cin>>acm_local_for_debug)
    {
        cin.putback(acm_local_for_debug);
        if (test_index_for_debug>100)
        {
            throw runtime_error("Check the stdin!!!");
        }
        auto start_clock_for_debug=clock();
        solve();
        auto end_clock_for_debug=clock();
        cout<<"\nTest "<<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;
    }
#else
    solve();
#endif
    return 0;
}

后记

感谢HDU-ILLLZKQF提供的思路协助。施聚nb!
DrGilbert 2020.10.8

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值