hdu4609(fft)
题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=4609
题意: 给出 n 根木棒求从中任取三根可以组成三角形的概率.
思路: fft
将 a 数组转化为 num 数组, 其中 num[i] 为长度为 i 的木棒的数目, 再做 num 和 num 的卷积, 这步可以用 fft 模板完成. 将卷积结果存入 num 中, 则 num[i] 为从 n 根木棒中任选两根且其长度和为 i 的组合数. 注意: 里面包含了自己和自己组合以及正反序的情况. 所以要先对 num 处理一下.
若两边的长和为 x (x > 0), 再有一条边长度小于 x 且大于 0, 那么这三条边一定可以组成三角形. 前面已经得到了 num 数组, 那么接下来可以枚举第三条边, 然后累计对答案的贡献. 为了避这里免重复计算, 先给 a 数组排序, 然后枚举 a[i] 作为当前三角形中的最长边, 累计对答案的贡献即可.
得到了能组成三角形的方案数 sol, 则概率为 sol / (n * (n - 1) * (n - 2) / 6).
代码:
1 #include <iostream> 2 #include <algorithm> 3 #include <math.h> 4 #include <string.h> 5 #include <stdio.h> 6 #define ll long long 7 using namespace std; 8 9 const double PI = acos(-1.0); 10 11 struct Complex{//复数结构体 12 double x, y;//实部,虚部 13 Complex(double _x = 0.0, double _y = 0.0){ 14 x = _x; 15 y = _y; 16 } 17 Complex operator -(const Complex &b) const{ 18 return Complex(x - b.x, y - b.y); 19 } 20 Complex operator +(const Complex &b) const{ 21 return Complex(x + b.x, y + b.y); 22 } 23 Complex operator *(const Complex &b) const{ 24 return Complex(x * b.x - y * b.y, x * b.y + y * b.x); 25 } 26 }; 27 28 //进行FFT和IFFT反转变化 29 //位置i和(i二进制反转后位置)互换 30 void change(Complex y[], int len){//len必须为2的幂 31 for(int i = 1, j = len / 2; i < len - 1; i++){ 32 if(i < j) swap(y[i], y[j]); //交换互为下标反转的元素,i<j保证只交换一次 33 int k = len >> 1; 34 while(j >= k){ 35 j -= k; 36 k /= 2; 37 } 38 if(j < k) j += k; 39 } 40 } 41 42 //做FFT,len必须为2的幂,on=1是DFT,on=-1是IDTF 43 void fft(Complex y[], int len, int on){ 44 change(y, len);//调用反转置换 45 for(int h = 2; h <= len; h <<= 1){ 46 Complex wn(cos(-on * 2 * PI / h), sin(-on * 2 * PI / h)); 47 for(int j = 0; j < len; j += h){ 48 Complex w(1, 0);//初始化螺旋因子 49 for(int k = j; k < j + h / 2; k++){//配对 50 Complex u = y[k]; 51 Complex t = w * y[k + h / 2]; 52 y[k] = u + t; 53 y[k + h / 2] = u - t; 54 w = w * wn;//更新螺旋因子 55 } 56 } 57 } 58 if(on == -1){ 59 for(int i = 0; i < len; i++){ 60 y[i].x /= len;//IDTF 61 } 62 } 63 } 64 65 const int MAXN = 4e5 + 10; 66 ll sum[MAXN], num[MAXN]; 67 Complex x1[MAXN]; 68 int a[MAXN]; 69 70 int main(void){ 71 int t, n; 72 scanf("%d", &t); 73 while(t--){ 74 scanf("%d", &n); 75 memset(num, 0, sizeof(num)); 76 memset(sum, 0, sizeof(sum)); 77 for(int i = 0; i < n; i++){ 78 scanf("%d", &a[i]); 79 num[a[i]]++; 80 } 81 sort(a, a + n); 82 int len = 1; 83 int len1 = a[n - 1] + 1; 84 while(len < (len1 << 1)) len <<= 1; 85 for(int i = 0; i < len1; i++){ 86 x1[i] = Complex(num[i], 0); 87 } 88 for(int i = len1; i < len; i++){ 89 x1[i] = Complex(0, 0); 90 } 91 fft(x1, len, 1); 92 for(int i = 0; i < len; i++){ 93 x1[i] = x1[i] * x1[i]; 94 } 95 fft(x1, len, -1);//IDFT(x1*x1) 96 for(int i = 0; i < len; i++){ 97 num[i] = (ll)(x1[i].x + 0.5);//四舍五入 98 } 99 //此时的num[i]为从a中选中任意两根木棒的和等于i的组合数,包含了自己和自己组合的情况以及正反序的组合情况 100 ll sol = 0; 101 len = a[n - 1] << 1; 102 for(int i = 0; i < n; i++){//减去自己和自己组合的 103 num[a[i] + a[i]]--; 104 } 105 for(int i = 0; i <= len; i++){//得到的组合数是无序的,所以要除2 106 num[i] /= 2; 107 } 108 for(int i = 1; i <= len; i++){ 109 sum[i] += sum[i - 1] + num[i];//sum[i]即从n根木棍中选出两根长度和大于等于i的方案数 110 } 111 for(int i = 0; i < n; i++){//当a[i]为三角形最长边时对答案的贡献 112 sol += sum[len] - sum[a[i]]; 113 sol -= (ll)(n - i - 1) * i;//减去有一条边大于a[i]的情况 114 sol -= (ll)(n - i - 1) * (n - i - 2) / 2;//减去两条边都大于a[i]的情况 115 sol -= n - 1;//减去包括a[i]的情况 116 } 117 ll cnt = (ll)n * (n - 1) * (n - 2) / 6; 118 double gel = sol * 1.0 / cnt; 119 printf("%.7lf\n", gel); 120 } 121 return 0; 122 }
我就是我,颜色不一样的烟火 --- geloutingyu