给定n条线段,随机选3条,输出三条线段能组成三角形的概率。
考虑暴力。枚举两条线段,第三条前缀和查询。 O(n2) O ( n 2 )
在 O(nlogn) O ( n l o g n ) 时间内 枚举两条线段不太好办。换种思路考虑。如果我们枚举最长边,去找2个短边之和>最长边的数量。
设
f(x)
f
(
x
)
为长度为x的线段数量。
求
f(x)
f
(
x
)
的卷积
g(x)
g
(
x
)
。即为两条线段凑到一起长度为x的组合的数量。
g(x)=∑f(x−i)f(i)
g
(
x
)
=
∑
f
(
x
−
i
)
f
(
i
)
这样会算重复线段本身还有每两个不相同的线段多算2遍,减完除一下就好。
这样我们可以得到两条不相同的线段加起来值为k的方案数。
枚举最长边。考虑如何计算合法答案。
一个三角形 我们枚举了一条边e,要求有2个不相同的边相加长度>e。
有三种情况不合法:
1.两条比他长的边组成的方案。
2.一条比他长一条比他短的边组成的方案。
3.他加上某条线段组成的方案。
减去即可计算出合法的方案数ans。
总方案数
C(n,3)
C
(
n
,
3
)
最终概率为
ans/C(n,3)
a
n
s
/
C
(
n
,
3
)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const double Pi=acos(-1);
const int MAXN=4e5+5;
struct cp{
double x,y;
cp(double xx=0,double yy=0){x=xx,y=yy;}
}a[MAXN],b[MAXN];
cp operator + (cp a,cp b){
return cp(a.x+b.x,a.y+b.y);
}
cp operator - (cp a,cp b){
return cp(a.x-b.x,a.y-b.y);
}
cp operator * (cp a,cp b){
return cp(a.x*b.x-a.y*b.y,a.x*b.y+b.x*a.y);
}
int l,r[MAXN],limit=1,n,h[MAXN],maxn=0;
ll g[MAXN];
void FFT(cp*A,int ty){
for(int i=0;i<limit;i++){
if(i<r[i])swap(A[i],A[r[i]]);
}
for(int mid=1;mid<limit;mid<<=1){
int R=mid<<1;
cp Wn(cos(Pi/mid),ty*sin(Pi/mid));
for(int j=0;j<limit;j+=R){
cp W(1,0);
for(int k=0;k<mid;k++){
cp x=A[j+k],y=W*A[j+k+mid];
A[j+k]=x+y;
A[j+k+mid]=x-y;
W=W*Wn;
}
}
}
}
void rework(){
l=0;limit=1;maxn=0;
memset(a,0,sizeof(a));
memset(b,0,sizeof(b));
memset(r,0,sizeof(r));
memset(g,0,sizeof(g));
memset(h,0,sizeof(h));
}
void work(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&h[i]);
maxn=max(maxn,h[i]);
a[h[i]]=a[h[i]]+1;b[h[i]]=b[h[i]]+1;
}
maxn*=2;
while(limit<=maxn+2)l++,limit<<=1;
for(int i=0;i<limit;i++){
r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
}
FFT(a,1);FFT(b,1);
for(int i=0;i<=limit;i++)a[i]=a[i]*b[i];
FFT(a,-1);
for(int i=0;i<=limit;i++)g[i]=ll(a[i].x/limit+0.5);
for(int i=1;i<=n;i++)g[h[i]<<1]--;
for(int i=0;i<=limit;i++)g[i]>>=1;
for(int i=1;i<=limit;i++)g[i]+=g[i-1];
sort(h+1,h+1+n);
ll ans=0;
for(int i=1;i<=n;i++){
// cout<<g[i]<<":"<<i<<endl;
ans+=g[limit]-g[h[i]];
ans-=1ll*(n-i)*(n-i-1)/2;//两边都>组成的g
ans-=1ll*(n-i)*(i-1);//一边大于一边小<组成的g
ans-=n-1;//他自己组成的g
}
//printf("%lld\n",ans);
printf("%.7lf\n",1.0*ans/((1ll*n*(n-1)*(n-2))/6));
}
int main(){
// freopen("1.in","r",stdin);
// freopen("1.out","w",stdout);
int T;
scanf("%d",&T);
while(T--){
rework();
work();
}
return 0;
}