链接: 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 ", ans); 117 } 118 119 return 0; 120 }