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 }
View Code

 

posted @ 2017-09-15 19:58  geloutingyu  阅读(236)  评论(0编辑  收藏  举报