HDU 4609 快速傅里叶变换

来源:互联网 发布:手机p正装照软件 编辑:程序博客网 时间:2024/06/05 13:23

题目链接

题意:已知n条边的长度,任取三条不同的边能够组成三角形的概率。


很经典的FFT
如果已知任意两条边的和的情况,那么枚举第三条边可以在O(n)的复杂度下得出总方案数。

难点便在于如果枚举两条边求和,其复杂度是不可承受的O(n2)

考虑用FFT优化该过程。
构造多项式,xn的系数是长度为n的边的条数。
类似于生成函数求方案数的思想。
该多项式与自身相乘便能得出任意两条边的组合情况。

此时应减去取了两条相同边的方案数,另外因为取边没有顺序,(边1+边2)与(边2+边1)是同一种情况,故每个系数应该除2。
然后处理一个前缀和就能快速求解啦~。

#include<cstdio>#include<cstdlib>#include<algorithm>#include<cstring>#include<cmath>using namespace std;typedef long long ll;const double pi = acos(-1.0);class comp{public:    double r,i;    comp(double _r = 0,double _i = 0){r = _r;i = _i;}    comp operator+(const comp x){return comp(r+x.r,i+x.i);}    comp operator-(const comp x){return comp(r-x.r,i-x.i);}    comp operator*(const comp x){return comp(r*x.r-i*x.i,r*x.i+i*x.r);}};void FFT(comp a[],int n,int t){    for(int i=1,j=0; i<n-1 ;i++){        for(int s=n;j^=s>>=1,~j&s;);        if(i<j) swap(a[i],a[j]);    }    for(int d=0;(1<<d)<n ;d++){        int m = 1<<d,m2 = m<<1;        double o = pi/m*t;comp _w(cos(o),sin(o));        for(int i=0 ;i<n ;i+=m2){            comp w(1,0);            for(int j=0 ;j<m ;j++){                comp &A = a[i+j+m],&B = a[i+j],t = w*A;                A = B-t;B = B+t;w = w*_w;            }        }    }    if(t==-1) for(int i=0 ;i<n ;i++) a[i].r/=n;}const int A = 1e5 + 10;comp x[A<<2];ll Ans[A<<2],all,ans;int cnt[A],a[A],len,m;void input(){    scanf("%d",&m);    memset(cnt,0,sizeof(cnt));    for(int i=0 ;i<m ;i++){        scanf("%d",&a[i]);        cnt[a[i]]++;    }    sort(a,a+m);    len = a[m-1] + 1;    all = 1LL*m*(m-1)*(m-2)/6;}void solve(){    int n = 1;    while(n < 2*len) n<<=1;    for(int i=0 ;i<len ;i++) x[i] = comp(cnt[i],0);    for(int i=len;i<n  ;i++) x[i] = comp(0,0);    FFT(x,n,1);    for(int i=0 ;i<n ;i++) x[i] = x[i]*x[i];    FFT(x,n,-1);    for(int i=0 ;i<n ;i++) Ans[i] = (ll)(x[i].r + 0.5);n = 2*a[m-1];    for(int i=0 ;i<m ;i++) Ans[2*a[i]]--;      //排除取相同边的情况    for(int i=0 ;i<=n;i++) Ans[i] /= 2;        //排除取边顺序不同的重复情况    for(int i=1 ;i<=n;i++) Ans[i] += Ans[i-1]; //处理前缀和    ans = 0;    for(int i=0 ;i<m ;i++){        ans += Ans[n] - Ans[a[i]];        ans -= 1LL*i*(m-i-1);                  //减去一大一小的情况        ans -= 1LL*(m-i-1)*(m-i-2)/2;          //减去两大的情况        ans -= 1LL*(m-1);                      //减去含自身的不合法情况    }    printf("%.7f\n",1.0*ans/all);}int main(){    int T;    scanf("%d",&T);    while(T--){        input();        solve();    }    return 0;}