HDU 4609 3-idiots

原题链接
题意:给定n个线段长度,问任意选3个组成三角形的概率。
n<=1e5

假如我们能求出任意两个的长度,就可以选出第3条边了。如我们求出siz[i]表示任意两个不同的线段的和等于i的个数,那么枚举最长的一条边a[i],那么任意两条比a[i]+1大的都行ans+=siz[a[i]+1]+...+siz[mx],其次要减去选了a[i]自己的可能就是n-1,其次就是选了一大一小(n-i)*(i-1),都比a[i]大(n-i)*(n-i-1)/2。
下面考虑如何求siz[i],这就是经典的fft问题。构造多项式\(A(x)=num[0]+num[1]*x^1+...+num[n]*x^n\),\(B(x)=num[0]+num[1]*x^1+...+num[n]*x^n\),那么C(x)的i+j的系数就是独立选两条的和,接着num[a[i]+a[i]]--,减去自己选了两次,num[i]/=2,去掉选了两次的情况,即选了a[i]与a[j],又选了a[j]与a[i]。

时间复杂度:\(O(nlogn)\)

#include <bits/stdc++.h>
using namespace std;                          
typedef long long ll;
const int maxn=2e6+10;
const double pi=acos(-1);
struct Complex{
    double x,y;
    friend Complex operator+(Complex x1,Complex y1)
    {
        return {x1.x+y1.x,x1.y+y1.y};
    }
    friend Complex operator-(Complex x1,Complex y1)
    {
        return {x1.x-y1.x,x1.y-y1.y};
    }
    friend Complex operator*(Complex x1,Complex y1)
    {
        return {x1.x*y1.x-x1.y*y1.y,x1.x*y1.y+x1.y*y1.x};
    }
}af[maxn],bf[maxn];
int rev[maxn];
void init(int n)
{
    rev[0]=0;
    for(int i=0;i<n;i++)
    {
        rev[i]=rev[i>>1]>>1;
        if(i&1) rev[i]|=n>>1;
    }
}
void fft(Complex a[],int n,int inv)
{
    for(int i=0;i<n;i++)
    if(i<rev[i]) swap(a[i],a[rev[i]]);
    for(int mid=1;mid<n;mid<<=1)
    {
        Complex it={cos(pi/mid),inv*sin(pi/mid)};
        for(int i=0;i<n;i+=mid*2)
        {
            Complex w={1,0};
            for(int j=0;j<mid;j++)
            {
                auto x=a[i+j],y=w*a[i+j+mid];
                a[i+j]=x+y;a[i+j+mid]=x-y;
                w=w*it;
            }
        }
    }
}
ll a[maxn],siz[maxn];
int main() 
{
    int t;cin>>t;
    while(t--)
    {
        int n;cin>>n;
        memset(siz,0,sizeof(siz));
        for(int i=1;i<=n;i++)
        cin>>a[i],siz[a[i]]++;
        sort(a+1,a+1+n);
        int cnt=1;
        while(cnt<2*a[n]+1) cnt*=2;
        af[0]=bf[0]={0,0};
        for(int i=1;i<cnt;i++)
        af[i]=bf[i]={1.0*siz[i],0};
        init(cnt);
        fft(af,cnt,1);fft(bf,cnt,1);
        for(int i=0;i<cnt;i++)
        af[i]=af[i]*bf[i];
        fft(af,cnt,-1);
        for(int i=0;i<cnt;i++)
        siz[i]=af[i].x/cnt+0.5;            //O(nlogn)

        for(int i=1;i<=n;i++)
        siz[2*a[i]]--;                    //减去自己取两次
        for(int i=0;i<cnt;i++)            //siz[i,j] 与 siz[j,i]重复计算
        siz[i]/=2;

        ll ans=0;int mx=2*a[n];
        for(int i=1;i<=mx;i++)
        siz[i]=siz[i]+siz[i-1];           //求前缀和
        for(int i=1;i<=n;i++)
        {
            ans+=siz[mx]-siz[a[i]];       //和比a[i]大的
            ans-=1ll*(n-i)*(n-i-1)/2;     //每个都比a[i]大
            ans-=n-1;                     //和a[i]重复
            ans-=1ll*(n-i)*(i-1);         //一大一小
        }
        ll ans1=1ll*n*(n-1)*(n-2)/6;
        printf("%.7f\n",1.0*ans/ans1);
    }
 	return 0;
}
posted @ 2021-08-18 09:15  ZJUT3  阅读(23)  评论(0)    收藏  举报