hdu 4609 3-idiots <FFT>


链接: http://acm.hdu.edu.cn/showproblem.php?pid=4609

题意: 给定 N 个正整数, 表示 N 条线段的长度, 问任取 3 条, 可以构成三角形的概率为多少~

数据范围: N<=10^5 ~~

思路:设三边分别为 x, y, z (x<=y<=z) 枚举 z ,统计 x+y 大于 z 的数目 .

比赛时能想到的只有 O(n^2) 的算法,无力 AC~

赛后才知道有种东西叫 FFT ~


以下为官方解题报告:

/*

  记录 A_i 为长度为 i 的树枝的数量,并让 A 对它本身做 FFT,得到任意选两个树枝能得到的各个和的数量。枚举第三边,

计算出所有两边之和大于第三条边的方案数,并把前两条边包含最长边的情况减掉就是答案。

*/

设长度为 a1,a2, ....an, 统计每种长度个数为 cnt[ ai ] ; 可表示多项式      

多项式自乘后, 其指数即为 ai + aj 的值, 系数即为 方案数, 减去不合法的即为所求~

  1 #include <cstdio>
  2 #include <cmath>
  3 #include <iostream>
  4 #include <algorithm>
  5 #include <cstring>
  6 using namespace std;
  7 typedef __int64 LL;
  8 const double pi=acos(-1);
  9 const int MAXN=3e5;
 10 const double eps=1e-6;
 11 struct C
 12 {
 13     double r, i;
 14     C (){}
 15     C(double _r, double _i):r(_r),i(_i){}
 16     inline C operator +(const C a)const{
 17     return C(r+a.r, i+a.i);    
 18     }
 19     inline C operator - (const C a)const {
 20         return C(r-a.r, i-a.i);    
 21     }
 22     inline C operator * (const C a)const{
 23         return C(r*a.r-i*a.i, r*a.i+i*a.r);    
 24     }
 25 }a[MAXN], b[MAXN];
 26 int num[MAXN], cnt[MAXN];
 27 LL res[MAXN], sum[MAXN];
 28 
 29 void brc(C *y,int l) // 二进制平摊反转置换 O(logn)
 30 {
 31     register int i,j,k;
 32     for(i=1,j=l>>1;i<l-1;i++){
 33         if(i<j)    swap(y[i],y[j]); // 交换互为下标反转的元素
 34                                 // i<j保证只交换一次
 35         k=l>>1;
 36         while(j>=k) {// 由最高位检索,遇1变0,遇0变1,跳出
 37             j-=k;
 38             k>>=1;
 39         }
 40         if(j<k)    j+=k;
 41     }
 42 }
 43 void FFT(C *y,int l,int on) // FFT O(nlogn)
 44                              // 其中on==1时为DFT,on==-1为IDFT
 45 {
 46     register int h,i,j,k;
 47     C u,t; 
 48     brc(y,l); // 调用反转置换
 49     for(h=2;h<=l;h<<=1) // 控制层数
 50     {
 51         // 初始化单位复根
 52         C wn(cos(on*2*pi/h),sin(on*2*pi/h));
 53         for(j=0;j<l;j+=h) // 控制起始下标
 54         {
 55             C w(1,0); // 初始化螺旋因子
 56             for(k=j;k<j+h/2;k++) // 配对
 57             {
 58                 u=y[k];
 59                 t=w*y[k+h/2];
 60                 y[k]=u+t;
 61                 y[k+h/2]=u-t;
 62                 w=w*wn; // 更新螺旋因子
 63             } // 据说上面的操作叫蝴蝶操作…
 64         }
 65     }
 66     if(on==-1)    for(i=0;i<l;i++)    y[i].r/=l; // IDFT
 67 }
 68 
 69 int n,  L , Max, T;
 70 int main() {
 71     scanf("%d", &T);
 72     while (T--) {
 73         Max = 0;
 74         memset(cnt, 0, sizeof(cnt));
 75         scanf("%d", &n);
 76         for (int i = 0; i < n; ++i) {
 77             scanf("%d", num + i);
 78             cnt[num[i]]++;
 79             Max = max(Max, num[i]);
 80         }
 81         ++Max;L = 1;
 82         while (L < Max <<1) {
 83             L <<= 1;
 84         }
 85         for (int i = 0; i < Max; ++i) {
 86             a[i] = C(cnt[i], 0);
 87         }
 88 
 89         for (int i = Max; i < L; ++i) {
 90             a[i] = C(0, 0);
 91         }
 92         FFT(a, L, 1);
 93         for (int i = 0; i < L; ++i)
 94             a[i] = a[i] * a[i]; // 多项式自乘
 95         FFT(a, L, -1);
 96 
 97         for (int i = 0; i < L; ++i) {
 98             res[i] = (LL) (a[i].r + 0.5);
 99         }
100 
101         for (int i = 0; i <= Max; ++i) 
102             res[i << 1] -= cnt[i];// 自己和自己乘, 即 枚举的是 x+x 的 
103     
104         for (int i = 0; i < L; ++i)
105             res[i] >>= 1; //  x+y 与  y+x 算一次 
106         for (int i = 1; i < L; ++i) {  //前缀和 
107             sum[i] = sum[i - 1] + res[i];     
108         }
109         double tot = 0, den =  1.0*n * (n - 1) * (n - 2)/6;
110         
111         for (int i = 0; i < n; ++i) {   
112             tot += sum[num[i]] / den;//  x+y<=z 的个数 
113         
114         }        
115         double ans = 1 - tot ;
116         printf("%.7f\n", ans);
117     }
118 
119     return 0;
120 }
View Code

 

posted @ 2013-07-26 12:50  淡墨æ末央  阅读(254)  评论(0编辑  收藏  举报