HDU 4609 3-idiots(FFT+组合计数)

3-idiots

Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 5921    Accepted Submission(s): 2059

Problem Description

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.

Input

An integer T(T≤100) 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).
The following line contains N integers a_i (1≤a_i≤105), which denotes the length of each branch, respectively.

Output

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

Sample Input

 
  
2 4 1 3 3 4 4 2 3 3 4

Sample Output

 
  
0.5000000 1.0000000

Source

2013 Multi-University Training Contest 1



        人生第二道FFT~

        本题题意简单,就是给你一组边长,然后问你,从这组边里面任意选择三条边,能够组成三角形的概率是多少?

        如果没有什么特殊的技巧直接取计算的话,非常的难做,而且暴力的话超时还不是超的一点点,所以我们考虑用统计的方法。我么考虑一个ans数组,ans[i]表示任意取两条边,它们的长度之和为i的方案数。如果已知这个ans数组,我们只需要O(N)的枚举第三条最长的边i,然后利用ans的前缀和数组sum,结果就是sum[maxlen]-sum[i]。那么现在就来考虑如果求这个ans数组。

        我们先考虑可以重复取的情况,我们记录一个num数组,num[i]表示长度为i的边的条数。那么我们很明显可以发现,ans数组恰好是num数组自身的卷积。这个可以说是一个母函数,相当于从两个边集中选取两条边来求和,指数代表长度,系数代表方案数,正好是一个母函数的模型。问题就转换成了求卷积,所以我们可以在O(NlogN)的时间内通过FFT求得卷积。但是,题目要求是不能重复取边,这个很容易解决,直接把所有的取相同边可以取得的长度的ans减去一即可。然后又因为本题求的是组合,所以还要把ans除以2。

        得出ans数组之后,我们就可以O(N)的枚举最长的那条便的长度,然后用sum[maxlen]-sum[i]求得方案数。但是,很快又发现,求得的方案不一定合法,因为另外两条边不一定比枚举的边i短,而如果枚举的边不是最长的则这种方法不正确。所以说我们要减去不合法的方案,不合法方案可以分为以下三种情况,我们把另外两条边的长度表示为x1和x2(这里的大于小于等于都是指排完序之后的位置):

                1、x1、x2中其中一个大于i时。总共有(n-i)*(i-1)种不合法的方案。

                2、x1或x2等于i时。总共有(n-1)种不合法的方案。

                3、x1和x2都大于i时。总共有(n-i)*(n-i-1)/2种不合法的方案。

        减去这些不合法方案后的结果就是最终的方案数,再除以总的取的方式就可以得到概率。另外还要注意,中间计算结果可能比较大,要用LL。具体见代码:

#include
#define PI acos(-1.0)
#define LL long long
#define N 300005
using namespace std;

struct Complex
{
    double r,i;
    Complex(double real=0.0,double image=0.0)
    {
        r=real; i=image;
    }

    Complex operator + (const Complex o)
    {
        return Complex(r+o.r,i+o.i);
    }
    Complex operator - (const Complex o)
    {
        return Complex(r-o.r,i-o.i);
    }
    Complex operator * (const Complex o)
    {
        return Complex(r*o.r-i*o.i,r*o.i+i*o.r);
    }
} x[N];

int n,a[N],num[N];
LL len,sum[N];

void brc(Complex *y, int l)
{
    register int i,j,k;
    for( i = 1, j = l / 2; i < l - 1; i++)
    {
        if (i < j) swap(y[i], y[j]);
        k = l / 2; while ( j >= k) j -= k,k /= 2;
        if (j < k) j += k;
    }
}
void FFT(Complex *y, int len, double on)
{
    register int h, i, j, k;
    Complex u, t; brc(y, len);
    for(h = 2; h <= len; h <<= 1)
    {
        Complex wn(cos(on * 2 * PI / h), sin(on * 2 * PI / h));
        for(j = 0; j < len; j += h)
        {
            Complex w(1, 0);
            for(k = j; k < j + h / 2; k++)
            {
                u = y[k]; t = w * y[k + h / 2];
                y[k] = u + t; y[k + h / 2] = u - t;
                w = w * wn;
            }
        }
    }
}

void multiply(Complex *A,int lenA)
{
    for(len = 1; len < 2 * lenA; len <<= 1);
    for (int i = lenA; i < len; i++) A[i] = 0;
    FFT(A,len , 1);
    for (int i = 0;i < len; i++) A[i] = A[i] * A[i];
    FFT(A, len, -1);
    for (int i = 0; i < len; i++) A[i].r/=len;
}

int main()
{
    int T_T;
    cin>>T_T;
    while(T_T--)
    {
        scanf("%d",&n);
        memset(x,0,sizeof(x));
        memset(num,0,sizeof(num));
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
            num[a[i]]++;
        }
        sort(a+1,a+n+1);
        for(int i=0;i>=1;
        for(int i=1;i<=len;i++)
            sum[i]+=sum[i-1];
        LL ans=0,tot=1LL*n*(n-1)*(n-2)/6;
        for(int i=1;i<=n;i++)
        {
            ans+=sum[len]-sum[a[i]];
            ans-=1LL*(n-i)*(i-1);
            ans-=1LL*(n-1);
            ans-=1LL*(n-i-1)*(n-i)/2;
        }
        printf("%.7f\n",(double) ans/tot);
    }
}


你可能感兴趣的:(---------Online,Judge--------,HDU,组合计数,FFT/NTT/FWT)