BZOJ3513-FFT,组合数学

BZOJ3513

题目描述

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

题解

考虑用总的方案数-不合法的方案数,显然不合法即为两边之和小于第三边的情况
a i a_i ai表示长度为 i i i的小木棍的数量, g i g_i gi表示长度大于等于 i i i的小木棍数量, f i f_i fi表示用2根小木棍拼成长度
i i i的方案数,则有
f i = ∑ j + k = i a i a k f_i=\sum\limits_{j+k=i}a_ia_k fi=j+k=iaiak
注意去重和选了同一条边的情况。
那么不合法的方案数即为
∑ f i g i \sum f_ig_i figi
再计算出总的方案数 C ( n , 3 ) C(n,3) C(n,3),最后计算概率即可

代码

#include  <cmath>
#include  <cstdio>
#include  <climits>
#include  <cstring>
#include  <cstdlib>
#include  <iostream>
#include  <algorithm>
#define int long long
using namespace std;
const int M=4e5+9;
const double pi=acos(-1.0);
int n,m,r[M],l,lim=1,num,ans,f[M],g[M],T;
struct complex{
	double x,y;
	complex(double xx=0,double yy=0){x=xx,y=yy;}
	friend inline complex operator+(complex a,complex b){return complex(a.x+b.x,a.y+b.y);}
	friend inline complex operator-(complex a,complex b){return complex(a.x-b.x,a.y-b.y);}
	friend inline complex operator*(complex a,complex b){return complex(a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x);}
	friend inline complex operator/(complex a,double b){return complex(a.x/b,a.y/b);}
	friend inline complex operator*(complex a,double b){return complex(a.x*b,a.y*b);}
}a[M];
void FFT(complex *A,int type){
	for(int i=0;i<lim;i++) if(i<r[i]) swap(A[i],A[r[i]]);
	for(int mid=1;mid<lim;mid<<=1){
		complex W(cos(pi/mid),type*sin(pi/mid));
		for(int R=mid<<1,j=0;j<lim;j+=R){
			complex w(1,0);
			for(int k=0;k<mid;k++,w=w*W){
				complex x=A[j+k],y=w*A[j+k+mid];
				A[j+k]=x+y;
				A[j+mid+k]=x-y;
			}
		}
	}
}
signed main(){
	scanf("%lld",&T);
	while(T--){
		memset(a,0,sizeof(a));
		memset(f,0,sizeof(f));
		memset(g,0,sizeof(g));
		memset(r,0,sizeof(r));
		l=ans=num=m=0;
		lim=1;
		scanf("%lld",&n);
		for(int i=1;i<=n;i++){
			int x;scanf("%lld",&x);
			a[x].x++;m=max(m,x);
			f[x*2]--;
		}for(int i=m;i>=1;i--) g[i]=g[i+1]+(long long)a[i].x;
		while(lim<m*2) lim<<=1,l++;
		for(int i=0;i<lim;i++) r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
		FFT(a,1);
		for(int i=0;i<=lim;i++) a[i]=a[i]*a[i];
		FFT(a,-1);
		for(int i=0;i<=lim;i++){
			f[i]+=(long long)(a[i].x/lim+0.5);
			f[i]=f[i]/2;
		}ans=num=n*(n-1)*(n-2)/6;
		for(int i=0;i<=m;i++) ans-=f[i]*g[i];
		//printf("%lld %lld\n",ans,num);
		printf("%.7lf\n",(double)ans/(double)num);
	}return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值