HDU#4609. 3-idiots


题目大意:

给定n个长度为ai的木棍
求,任选三个使其能够组成三角形的概率



分析:

ai <= 1e5,先统计c[i]代表长度为i的木棍有多少个
对c做卷积,即使用FFT求出 rep(i,0,1e5) rep(j,0,1e5) d[i+j]+=c[i]*c[j] 的d数组
d[i]即代表选出两个木棍长度之和为i的方案数
去除重复,减去使用了同一木棍两次的,i+j与j+i应为同一方案,所以rep(i,0,2e5) d[i]/=2
现在d[i]已经可以表示选出两个不同木棍长度之和为i的不同方案数了

接下来对木棍长度ai从小到大排序,对d做前缀和sd
枚举最长边为第i根木棍,则另外两条边长度之和>ai,ans+=sd[2e5]-sd[ai]
这些方案里需要去除一些不满足要求(ai为最长边)的
1.另外两条边两条均>ai,ans-=(n-i)*(n-i-1)/2
2.另外两条边一条>ai,一条<ai,ans-=(n-i)*(i-1)
3.另外两条边一条=ai,另一条随意,ans-=n-1


附上代码:

/*     ai <= 1e5,先统计c[i]代表长度为i的木棍有多少个
     对c做卷积,即使用FFT求出 rep(i,0,1e5) rep(j,0,1e5) d[i+j]+=c[i]*c[j] 的d数组
     d[i]即代表选出两个木棍长度之和为i的方案数
     去除重复,减去使用了同一木棍两次的,i+j与j+i应为同一方案,所以rep(i,0,2e5) d[i]/=2
     现在d[i]已经可以表示选出两个不同木棍长度之和为i的不同方案数了
 
     接下来对木棍长度ai从小到大排序,对d做前缀和sd
     枚举最长边为第i根木棍,则另外两条边长度之和>ai,ans+=sd[2e5]-sd[ai]
     这些方案里需要去除一些不满足要求(ai为最长边)的
     1.另外两条边两条均>ai,ans-=(n-i)*(n-i-1)/2
     2.另外两条边一条>ai,一条<ai,ans-=(n-i)*(i-1)
     3.另外两条边一条=ai,另一条随意,ans-=n-1*/
#include<bits/stdc++.h>
using namespace std;
const int maxn=4e5+12;
const double pi=acos(-1);
int n,N;
long long num[maxn],branch[maxn];

struct Complex
{
    double x,i;
    Complex(){}
    Complex(double a,double b) {x=a;i=b;}
}A[maxn];
Complex operator + (Complex a,Complex b) {return Complex(a.x+b.x,a.i+b.i);}
Complex operator - (Complex a,Complex b) {return Complex(a.x-b.x,a.i-b.i);}
Complex operator * (Complex a,Complex b) {return Complex(a.x*b.x-a.i*b.i,a.x*b.i+a.i*b.x);}

int rev[maxn];
void FFT(Complex *a,int t)
{
    for(int i=0;i<n;i++) if(rev[i]>i) swap(a[i],a[rev[i]]);

    for(int i=1;i<n;i<<=1)
    {
        Complex wn(cos(2*pi/(i<<1)),t*sin(2*pi/(i<<1)));
        for(int j=0;j<n;j+=(i<<1))
        {
            Complex w(1,0),t0,t1;
            for(int k=0;k<i;k++) 
            {
                t0=a[j+k];t1=w*a[i+j+k];
                a[j+k]=t0+t1;
                a[i+j+k]=t0-t1;
                w=w*wn;
            }
        }
    }
}
long long sum[maxn];
int main()
{
    freopen("a.in","r",stdin);
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d",&N);
        memset(num,0,sizeof(num));
        for(int i=0;i<N;i++) scanf("%d",&branch[i]),num[branch[i]]++;
        sort(branch,branch+N);//从小到大排序 
        
        int imax=branch[N-1]+1;//最大的数     
        n=1;int len=0;//n为2的len次方 n要比2*imax-1大 避免冲突 
        while(n<imax*2) n<<=1,len++;//两个imax相乘 会更新2*imax的位置 
        
        for(int i=0;i<imax;i++) A[i]=Complex(num[i],0);// 
        for(int i=imax;i<n;i++) A[i]=Complex(0,0);
        
        rev[0]=0;
        for(int i=1;i<n;i++) rev[i]=(rev[i>>1]>>1)|((i&1)<<(len-1));//len是要他们的二进制长度+1 
    
        FFT(A,1);
    
        for(int i=0;i<n;i++) A[i]=A[i]*A[i];//卷积 
        FFT(A,-1);
        
        for(int i=0;i<n;i++) num[i]=(long long)(A[i].x/n+0.5);//求出num 
        imax=2*branch[N-1];//最大的和为imax  
        for(int i=0;i<N;i++) num[branch[i]*2]--;//把自己和自己的组成的都减去 
        for(int i=1;i<=imax;i++) num[i]/=2;//1 2与2 1我们计算了两次 所有整体除二 
        sum[0]=0;
        for(int i=1;i<=imax;i++) sum[i]=sum[i-1]+num[i];
        
        long long cnt=0;
        for(int i=0;i<N;i++) 
        {
            cnt+=sum[imax]-sum[branch[i]];
            //减掉一个取大,一个取小的
            cnt-=(long long)(N-1-i)*i;
            //减掉一个取本身,另外一个取其它
            cnt-=(N-1);
            //减掉大于它的取两个的组合
            cnt-=(long long)(N-1-i)*(N-i-2)/2;
        }
        long long tot = (long long)N*(N-1)*(N-2)/6;
        printf("%.7lf
",(double)cnt/tot);
    }
    return 0;
}
View Code

原文地址:https://www.cnblogs.com/Heey/p/9055495.html