【BZOJ 3513】idiots

传送门


Problem

给定 n n n 个长度分别为 a i a_i ai 的木棒,问随机选择 3 3 3 个木棒能够拼成三角形的概率。

数据范围: 3 ≤ n ≤ 1 0 5 3≤n≤10^5 3n105 1 ≤ a i ≤ 1 0 5 1≤a_i≤10^5 1ai105


Solution

我们设 c n t i cnt_i cnti 表示 i i i 的出现次数,用 f i f_i fi 表示用两根木棒能拼出长度为 i i i 的方案数,有:

f i = ∑ j = 0 i c n t i × c n t i − j f_i=\sum_{j=0}^{i}cnt_i\times cnt_{i-j} fi=j=0icnti×cntij

这是一个卷积的式子,可以 f f t fft fft 优化。

要注意两点:

  1. i i i 是偶数时, f i f_i fi 就会加上 c n t i / 2 × c n t i / 2 cnt_{i/2}\times cnt_{i/2} cnti/2×cnti/2,就有可能会有选两根相同的木棒,这是不合法的。
  2. 会算重,例如 f 4 f_4 f4 会算到两个 c n t 1 × c n t 3 cnt_1\times cnt_3 cnt1×cnt3,因此最后要除 2 2 2

因此对于 i i i,若 i i i 是奇数, F i = f i 2 F_i=\frac{f_i}{2} Fi=2fi;否则 F i = f i − c n t i / 2 2 F_i=\frac{f_i-cnt_{i/2}}{2} Fi=2ficnti/2

我们记后缀和 S i S_i Si 表示长度大于等于 i i i 的木棒有多少个。

那么不能拼出来的数量就是 ∑ F i × S i \sum F_i\times S_i Fi×Si,由于总数是 C n 3 = n ! 3 ! × ( n − 3 ) ! = n ( n − 1 ) ( n − 2 ) 6 C_n^3=\frac{n!}{3!\times(n-3)!}=\frac{n(n-1)(n-2)}{6} Cn3=3!×(n3)!n!=6n(n1)(n2),答案就很显然了。


Code

#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 300005
#define ll long long
using namespace std;
int n,Max,limit,pos[N];
const double pi=acos(-1);
struct complex{
	double x,y;
	complex(){}
	complex(double x,double y):x(x),y(y){}
	friend complex operator+(const complex &a,const complex &b)  {return complex(a.x+b.x,a.y+b.y);}
	friend complex operator-(const complex &a,const complex &b)  {return complex(a.x-b.x,a.y-b.y);}
	friend complex operator*(const complex &a,const complex &b)  {return complex(a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x);}
	friend complex operator/(const complex &a,double b)  {return complex(a.x/b,a.y/b);}
}a[N];
ll A,B,f[N];
int g[N],cnt[N];
void Clear(){
	Max=0,limit=1;
	memset(a,0,sizeof(a));
	memset(g,0,sizeof(g));
	memset(cnt,0,sizeof(cnt));
}
void DFT(complex f[],int type){
	for(int i=0;i<limit;++i)
		if(pos[i]>i)  swap(f[i],f[pos[i]]);
	for(int mid=1;mid<limit;mid<<=1){
		complex now=complex(cos(pi/mid),type*sin(pi/mid));
		for(int i=0;i<limit;i+=(mid<<1)){
			complex w=complex(1,0);
			for(int j=0;j<mid;++j,w=w*now){
				complex p0=f[i+j],p1=w*f[i+j+mid];
				f[i+j]=p0+p1,f[i+j+mid]=p0-p1;
			}
		}
	}
	if(type==-1)  for(int i=0;i<limit;++i)  f[i].x/=limit;
}
int main(){
	int T;
	scanf("%d",&T);
	while(T--){
		Clear();
		scanf("%d",&n);
		for(int i=1,x;i<=n;++i){
			scanf("%d",&x);
			a[x].x++,cnt[x]++;
			Max=max(Max,x);
		}
		for(int i=Max;i>=1;--i)  g[i]=g[i+1]+cnt[i];
		while(limit<=(Max<<1))  limit<<=1;
		for(int i=0;i<limit;++i)  pos[i]=(pos[i>>1]>>1)|((i&1)*(limit>>1));
		DFT(a,1);
		for(int i=0;i<limit;++i)  a[i]=a[i]*a[i];
		DFT(a,-1);
		for(int i=1;i<=Max;++i){
			f[i]=(ll)(a[i].x+0.5);
			if(!(i&1))  f[i]-=cnt[i>>1];
			f[i]>>=1;
		}
		A=B=(ll)n*(n-1)*(n-2)/6;
		for(int i=1;i<=Max;++i)  A-=(ll)f[i]*g[i];
		printf("%.7lf\n",(double)A/(double)B);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值