HDU-4609

HDU-4609

3-idiots

题意

t组测试,每组测试给出n条木棒 n<1e5

然后输入n条木棒的长度ai,问等概率取出三条木棒能构成三角形的概率

题解

参考题解A 参考题解B

首先将读入信息从每个木棒长度 变为 每种长度几根木棒

例如lenth={1,3,3,4} 变为 num={0,1,0,2,1}

即长度为0的有0根,1的有1根,…..

那么如果从它们中任选两根组成三角形的两边和 那么可以得到

num={0, 0, 1, 0, 4, 2, 4, 4, 1}

(其中有AB互换得到的和以及同一根木棍自己和自己相加得到的和,之后会减去)

假如用生成函数来描述 那么 {0,1,0,2,1} 记为$f(x)=0x^0+1x^1+0x^2+2x^3+1x^4$

而两两匹配得到的{0, 0, 1, 0, 4, 2, 4, 4, 1}

实际上就是$f(x)×f(x)=0x^0+0x^1+1x^2+0x^3+4x^4+2x^5+4x^6+4x^7+1x^8$

其中次幂i表示长度,系数ai表示有多少根木棍=该长度i

于是我们可以利用FFT求出木棍两两组合得到新的长度各有多少种

另外需要容斥排除掉一些情况,在代码的注释中会有解释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define endl '\n'
#define Turnoff std::ios::sync_with_stdio(false)
const ll Max=3e5+5;
const double Pi=acos(-1);
/*
*/
struct Complex{
double r,i;
Complex(){r=0,i=0;}
Complex(double real,double imag):r(real),i(imag){};
};
Complex operator + (Complex a, Complex b){return Complex(a.r+b.r,a.i+b.i);}
Complex operator - (Complex a, Complex b){return Complex(a.r-b.r,a.i-b.i);}
Complex operator * (Complex a, Complex b){return Complex(a.r*b.r-a.i*b.i,a.r*b.i+a.i*b.r);}

ll rev[Max],len=0,lim=1,ans[Max];
Complex a[Max],b[Max];///原多项式系数
ll sum[Max],lenth[Max],num[Max];
///预处理位逆变换
inline void getrev(int lena,int lenb){
int limit=1,lenth=0;
while(limit<=lena+lenb)limit<<=1,lenth++;
for(int i=0;i<limit;i++)rev[i]=(rev[i>>1]>>1)|((i&1)<<(lenth-1));
lim=limit,len=lenth;
return;
}
inline void FFT(Complex *A,int opt){
for(int i=0;i<lim;i++)if(i<rev[i]) swap(A[i],A[rev[i]]);///求出要迭代的序列
for(int mid=1;mid<lim;mid<<=1){///待合并区间长度的一半
Complex Wn=Complex(cos(Pi/mid),opt*sin(Pi/mid));
for(int R=mid<<1,i=0;i<lim;i+=R){///R是区间的右端点,i表示前已经到哪个位置了
Complex W=Complex(1,0);
for(int k=0;k<mid;k++){///枚举左半部分
Complex x=A[i+k],y=W*A[i+mid+k];///蝴蝶操作
A[i+k]=x+y;
A[i+mid+k]=x-y;
W=W*Wn;
}
}
}
if(opt==-1)for(int i=0;i<lim;i++)A[i].r=(A[i].r/lim+0.5);
}
int main() {
//Turnoff;
int t;
scanf("%d",&t);
while(t--){
int n;scanf("%d",&n);
ll maxlen=0,tot=1ll*n*(n-1)*(n-2)/6;
memset(a,0,sizeof a);
memset(sum,0,sizeof sum);
//memset(num,0,sizeof num);
for(int i=0;i<n;i++){
scanf("%lld",lenth+i);
a[lenth[i]].r++;
//num[lenth[i]]++;
maxlen=max(maxlen,lenth[i]);
}
getrev(maxlen,maxlen);FFT(a,1);
maxlen*=2;
for(int i=0;i<lim;i++)a[i]=a[i]*a[i];
FFT(a,-1);
for(int i=0;i<lim;i++)sum[i]=a[i].r;
for(int i=0;i<n;i++)sum[lenth[i]*2]--;
///同一根木棒自己加自己 不合法减去
for(int i=0;i<=maxlen;i++)sum[i]/=2;
/// 两根木棒AB互换后BA重复统计 不合法/2
for(int i=1;i<=maxlen;i++)sum[i]+=sum[i-1];
/// 做前缀和处理
ll ans=0;
sort(lenth,lenth+n);
///排序构成三角形的第三边(最长边)长度
///枚举最长边
for(int i=0;i<n;i++){
ans+=sum[maxlen]-sum[lenth[i]];
///根据定理两边之和大于第三边得到两边和大于枚举边长度lenth[i]的总数
///我们假定lenth[i]最长
ans-=1ll*i*(n-i-1);
///两边中有一个大于lenth[i]一个小于lenth[i]
ans-=1ll*(n-i-1)*(n-i-2)/2;
///两边都大于lenth[i]
ans-=1ll*(n-1);
///另外两边中有一个就是lenth[i]本身
}
//cout<<ans<<" "<<tot<<endl;
printf("%.7f\n",1.0*ans/tot);
}
}