HDU 4609 3-idiots(FFT)


3-idiots

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


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≤10 5).
The following line contains N integers a_i (1≤a_i≤10 5), 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
 

 

Recommend
liuyiding
 

 

 

 

學會了FFT。

這題就很容易了。

其實題目是給了n條線段。問隨機取三個,可以組成三角形的概率。

 

其實就是要求n條線段,選3條組成三角形的選法有多少種。

 

首先題目給了a數組,

如樣例一:

4

1 3 3 4

把這個數組轉化成num數組,num[i]表示長度為i的有num[i]條。

樣例一就是

num = {0   1   0    2    1}

代表長度0的有0根,長度為1的有1根,長度為2的有0根,長度為3的有兩根,長度為4的有1根。

使用FFT解決的問題就是num數組和num數組卷積。

num數組和num數組卷積的解決,其實就是從{1 3 3 4}取一個數,從{1 3 3 4}再取一個數,他們的和每個值各有多少個

例如{0 1 0 2 1}*{0 1 0 2 1} 卷積的結果應該是{0 0  1  0  4  2  4  4  1 }

長度為n的數組和長度為m的數組卷積,結果是長度為n+m-1的數組。

 

{0 1 0 2 1}*{0 1 0 2 1} 卷積的結果應該是{0 0  1  0  4  2  4  4  1 }。

這個結果的意義如下:

從{1 3 3 4}取一個數,從{1 3 3 4}再取一個數

取兩個數和為 2 的取法是一種:1+1

           和為 4 的取法有四種:1+3, 1+3  ,3+1 ,3+1

           和為 5 的取法有兩種:1+4 ,4+1;

           和為 6的取法有四種:3+3,3+3,3+3,3+3,3+3

           和為 7 的取法有四種: 3+4,3+4,4+3,4+3

           和為 8 的取法有 一種:4+4

 

利用FFT可以快速求取循環卷積,具體求解過程不解釋了,就是DFT和FFT的基本理論了。

總之FFT就是快速求到了num和num卷積的結果。只要長度滿足>=n+m+1.那么就可以用循環卷積得到線性卷積了。

 

弄完FFT得到一個num數組,這個數組的含義在上面解釋過了。

 

        while( len < 2*len1 )len <<= 1;
        for(int i = 0;i < len1;i++)
            x1[i] = complex(num[i],0);
        for(int i = len1;i < len;i++)
            x1[i] = complex(0,0);
        fft(x1,len,1);
        for(int i = 0;i < len;i++)
            x1[i] = x1[i]*x1[i];
        fft(x1,len,-1);
        for(int i = 0;i < len;i++)
            num[i] = (long long)(x1[i].r+0.5);

這里代碼中的num數組就是卷積后的結果,表示兩兩組合。

但是題目中本身和本身組合是不行的,所有把取同一個的組合的情況刪掉。

        //減掉取兩個相同的組合
        for(int i = 0;i < n;i++)
            num[a[i]+a[i]]--;

還有,這個問題求組合,所以第一個選t1,第二個選t2,和第一個選t2,第二個選t1,我們認為是一樣的。

所有num數組整體除於2

        //選擇的無序,除以2
        for(int i = 1;i <= len;i++)
        {
            num[i]/=2;
        }

然后對num數組求前綴和

        sum[0] = 0;
        for(int i = 1;i <= len;i++)
            sum[i] = sum[i-1]+num[i];

 

之后就開始O(n)找可以形成三角形的組合了。

a數組從小到大排好序。

 

對於a[i].  我們假設a[i]是形成的三角形中最長的。這樣就是在其余中選擇兩個和>a[i],而且長度不能大於a[i]的。(注意這里所謂的大於小於,不是說長度的大於小於,其實是排好序以后的,位置關系,這樣就可以不用管長度相等的情況,排在a[i]前的就是小於的,后面的就是大於的)。

根據前面求得的結果。

長度和大於a[i]的取兩個的取法是sum[len]-sum[a[i]].

但是這里面有不符合的。

一個是包含了取一大一小的

cnt -= (long long)(n-1-i)*i;

一個是包含了取一個本身i,然后取其它的

cnt -= (n-1);

還有就是取兩個都大於的了

cnt -= (long long)(n-1-i)*(n-i-2)/2;

 

這樣把i從0~n-1累加,就答案了。

 

        long long cnt = 0;
        for(int i = 0;i < n;i++)
        {
            cnt += sum[len]-sum[a[i]];
            //減掉一個取大,一個取小的
            cnt -= (long long)(n-1-i)*i;
            //減掉一個取本身,另外一個取其它
            cnt -= (n-1);
            //減掉大於它的取兩個的組合
            cnt -= (long long)(n-1-i)*(n-i-2)/2;
        }

 

 

 

 

 

 

 

使用FFT一定要注意控制好長度,長度要為2^k.而且大於等於len1+len2-1.這樣可以保證不重疊。

 

然后就是用long long,有的地方會超int的。

 

貼上代碼:

#include <stdio.h>
#include <iostream>
#include <string.h>
#include <algorithm>
#include <math.h>
using namespace std;

const double PI = acos(-1.0);
struct complex
{
    double r,i;
    complex(double _r = 0,double _i = 0)
    {
        r = _r; i = _i;
    }
    complex operator +(const complex &b)
    {
        return complex(r+b.r,i+b.i);
    }
    complex operator -(const complex &b)
    {
        return complex(r-b.r,i-b.i);
    }
    complex operator *(const complex &b)
    {
        return complex(r*b.r-i*b.i,r*b.i+i*b.r);
    }
};
void change(complex y[],int len)
{
    int i,j,k;
    for(i = 1, j = len/2;i < len-1;i++)
    {
        if(i < j)swap(y[i],y[j]);
        k = len/2;
        while( j >= k)
        {
            j -= k;
            k /= 2;
        }
        if(j < k)j += k;
    }
}
void fft(complex y[],int len,int on)
{
    change(y,len);
    for(int h = 2;h <= len;h <<= 1)
    {
        complex wn(cos(-on*2*PI/h),sin(-on*2*PI/h));
        for(int j = 0;j < len;j += h)
        {
            complex w(1,0);
            for(int k = j;k < j+h/2;k++)
            {
                complex u = y[k];
                complex t = w*y[k+h/2];
                y[k] = u+t;
                y[k+h/2] = u-t;
                w = w*wn;
            }
        }
    }
    if(on == -1)
        for(int i = 0;i < len;i++)
            y[i].r /= len;
}

const int MAXN = 400040;
complex x1[MAXN];
int a[MAXN/4];
long long num[MAXN];//100000*100000會超int
long long sum[MAXN];

int main()
{
    int T;
    int n;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d",&n);
        memset(num,0,sizeof(num));
        for(int i = 0;i < n;i++)
        {
            scanf("%d",&a[i]);
            num[a[i]]++;
        }
        sort(a,a+n);
        int len1 = a[n-1]+1;
        int len = 1;
        while( len < 2*len1 )len <<= 1;
        for(int i = 0;i < len1;i++)
            x1[i] = complex(num[i],0);
        for(int i = len1;i < len;i++)
            x1[i] = complex(0,0);
        fft(x1,len,1);
        for(int i = 0;i < len;i++)
            x1[i] = x1[i]*x1[i];
        fft(x1,len,-1);
        for(int i = 0;i < len;i++)
            num[i] = (long long)(x1[i].r+0.5);
        len = 2*a[n-1];
        //減掉取兩個相同的組合
        for(int i = 0;i < n;i++)
            num[a[i]+a[i]]--;
        //選擇的無序,除以2
        for(int i = 1;i <= len;i++)
        {
            num[i]/=2;
        }
        sum[0] = 0;
        for(int i = 1;i <= len;i++)
            sum[i] = sum[i-1]+num[i];
        long long cnt = 0;
        for(int i = 0;i < n;i++)
        {
            cnt += sum[len]-sum[a[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\n",(double)cnt/tot);
    }
    return 0;
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM