给定 n n 个长度分别为 ai a i 的木棒,问随机选择 3 3 根木棒能够拼成三角形的概率。
对于 100% 100 % 的数据,最多100组数据,且满足 1⩽n,ai⩽105 1 ⩽ n , a i ⩽ 10 5 。
拼成三角形的充要条件就是满足三角不等式。
三角不等式,即在三角形中两边之和大于第三边。
考虑到能拼成三角形的条件有些苛刻,这里先计算不能拼成三角形的概率,之后用总方案去减。
总方案为:从 n n 根木棒中选取 3 3 根木棒的方案数量为 C3n=n(n−1)(n−2)3×2×1 C n 3 = n ( n − 1 ) ( n − 2 ) 3 × 2 × 1
而不能拼成三角形即两边之和小于等于第三边。
设 ti t i 为长度为 i i 的木棒的数量, fi f i 为两根木棒的长度和为 i i 的方案数。
直接暴力计算的时候,直接把两种长度和为 i i 的木棍数量乘起来就行了。
即 fi=∑j=1i−1tj×ti−j f i = ∑ j = 1 i − 1 t j × t i − j 。
如果你和我说计算 f f 数组是 O(n2) O ( n 2 ) 的,会超时,那你的FFT白学了。可以发现这就是卷积的形式。
下面考虑重复部分。哪些地方会有重复计算?举个例子。
两根长度为 1 1 的,三根长度为 2 2 的,则 t[1⋯2]={2,3} t [ 1 ⋯ 2 ] = { 2 , 3 } , f[1⋯4]={0,4,12,9} f [ 1 ⋯ 4 ] = { 0 , 4 , 12 , 9 } 。
我们发现,当 i i 为偶数时,组成 fi f i 的一部分为 t2i t i 2 。这里面算上了选同一根木棒两次的情况,因此要减去 ti t i 。
修正后 f[1⋯4]={0,2,12,6} f [ 1 ⋯ 4 ] = { 0 , 2 , 12 , 6 } 。
又发现,选择木棒 a a 和木棒 b b 与选择木棒 b b 和木棒 a a 是等价的,因此需要除以 2 2 。
修正后 f[1⋯4]={0,1,6,3} f [ 1 ⋯ 4 ] = { 0 , 1 , 6 , 3 } 。
下面考虑计算出 f f 数组后如何统计答案。
加入第三根木棒,当第三根木棒长度为 k k 时,所有长度和小于等于 k k 的另外两根木棒组合在一起都是非法的。即所有 fi(1⩽i⩽k) f i ( 1 ⩽ i ⩽ k ) 都不能组成三角形。对不能组成三角形的答案的贡献为 tk×∑i=1kfi t k × ∑ i = 1 k f i ,其中 ∑i=1kfi ∑ i = 1 k f i 可以递推 O(n) O ( n ) 计算。
最后用总数减去非法数的差除以总数即得合法概率。
f f 数组记得开 long long l o n g l o n g ,总数记得开 long long l o n g l o n g ,非法数记得开 long long l o n g l o n g 。
重要的事情说三遍。
没开 long long l o n g l o n g 卡了我 3h 3 h 。平均一个小时找出一个地方没开 long long l o n g l o n g 我能怎么办。
#include
using namespace std;
const int maxn=300010;
const double pi=acos(-1.0);
struct comp{
double x,y;
comp(double xx=0,double yy=0):x(xx),y(yy) {}
friend comp operator+(const comp &x,const comp &y) {return comp(x.x+y.x,x.y+y.y);}
friend comp operator-(const comp &x,const comp &y) {return comp(x.x-y.x,x.y-y.y);}
friend comp operator*(const comp &a,const comp &b) {return comp(a.x*b.x-a.y*b.y,a.x*b.y+b.x*a.y);}
}a[maxn];
int limit=1,r[maxn];
void fft(comp *t,int ty=1){
for(int i=0;iif(ifor(int mid=1;mid1){
comp wn(cos(pi/mid),ty*sin(pi/mid));
for(int j=0,R=(mid<<1);j1,0);
for(int k=0;k#define ifft(a) fft(a,-1)
int t[maxn],T,n,mx;
long long f[maxn];//1. 记得开long long
int main(void){
scanf("%d",&T);
while(T--){
memset(a,0,sizeof a);
memset(r,0,sizeof r);
memset(t,0,sizeof t);
limit=1;mx=0;
scanf("%d",&n);
for(int i=1,x;i<=n;i++){
scanf("%d",&x);
mx=max(mx,x);
++t[x];
}
for(int i=1;i<=mx;i++)//减少精度误差
a[i]=comp(t[i],0);
int ln=mx<<1,l=0;//最长长度小于最长的木棒的两倍
while(limit<=ln)
limit<<=1,++l;
for(int i=1;i>1]>>1)|((i&1)<<(l-1)));
fft(a);
for(int i=0;i//2. 记得开long long
long long tot=(long long)n*(n-1)*(n-2)/6,ans=0,sum=0;
for(int i=1;i<=mx;i++){
f[i]=floor(a[i].x/limit+0.5);//减少精度误差
if(i%2==0)//去掉重复部分
f[i]-=t[i>>1];
f[i]>>=1;
}
for(int i=1;i<=mx;i++)
f[i]+=f[i-1];//前缀和
for(int i=1;i<=mx;i++)
ans+=(long long)t[i]*f[i];//3. 记得开long long
printf("%.7lf\n",1-(double)ans/tot);
}
return 0;
}