题目大意
给定 个长度分别为 的木棒,问随机选择 根木棒能够拼成三角形的概率。
数据范围
对于 的数据,最多100组数据,且满足 。
题解
拼成三角形的充要条件就是满足三角不等式。
三角不等式,即在三角形中两边之和大于第三边。
考虑到能拼成三角形的条件有些苛刻,这里先计算不能拼成三角形的概率,之后用总方案去减。
总方案为:从
根木棒中选取
根木棒的方案数量为
而不能拼成三角形即两边之和小于等于第三边。
设
为长度为
的木棒的数量,
为两根木棒的长度和为
的方案数。
直接暴力计算的时候,直接把两种长度和为
的木棍数量乘起来就行了。
即
。
如果你和我说计算
数组是
的,会超时,那你的FFT白学了。可以发现这就是卷积的形式。
下面考虑重复部分。哪些地方会有重复计算?举个例子。
两根长度为
的,三根长度为
的,则
,
。
我们发现,当
为偶数时,组成
的一部分为
。这里面算上了选同一根木棒两次的情况,因此要减去
。
修正后
。
又发现,选择木棒
和木棒
与选择木棒
和木棒
是等价的,因此需要除以
。
修正后
。
下面考虑计算出
数组后如何统计答案。
加入第三根木棒,当第三根木棒长度为
时,所有长度和小于等于
的另外两根木棒组合在一起都是非法的。即所有
都不能组成三角形。对不能组成三角形的答案的贡献为
,其中
可以递推
计算。
最后用总数减去非法数的差除以总数即得合法概率。
代码
数组记得开
,总数记得开
,非法数记得开
。
重要的事情说三遍。
没开
卡了我
。平均一个小时找出一个地方没开
我能怎么办。
#include<bits/stdc++.h>
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;i<limit;i++)
if(i<r[i])
swap(t[i],t[r[i]]);
for(int mid=1;mid<limit;mid<<=1){
comp wn(cos(pi/mid),ty*sin(pi/mid));
for(int j=0,R=(mid<<1);j<limit;j+=R){
comp w(1,0);
for(int k=0;k<mid;k++,w=w*wn){
comp x=t[j+k],y=w*t[j+k+mid];
t[j+k]=x+y;
t[j+k+mid]=x-y;
}
}
}
}
#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<limit;i++)
r[i]=((r[i>>1]>>1)|((i&1)<<(l-1)));
fft(a);
for(int i=0;i<limit;i++)
a[i]=a[i]*a[i];
ifft(a);
//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;
}