Bzoj3513:[MUTC2013]idiots:FFT

3 篇文章 0 订阅

题目链接::[MUTC2013]idiots

设s[i]长度为i的木棒出现的次数,dp[i]为两根木棍组成长度为i的方案数

那么

这是一个卷积的式子,可以用FFT来加速了

统计答案用总概率减去不合法的概率

我们一遍扫过去,扫到i时统计sum为dp[1->i]的和

因为当木根长度为i时sum中的所有方案都非法,随意非法答案总数加上sum*s[i]

注意如果i为偶数时,dp[i]中会有i/2+i/2这种方案,所以要去重

最后非法答案不要忘了/2

#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define LL long long
using namespace std;
const int maxn=444440;
const double pi=3.141592653589793238462643383279502884197169399375105820974944;
struct cp{
 	double r,i;
	cp(double _r=0,double _i=0):r(_r),i(_i){}
	cp operator + (cp x){return cp(r+x.r,i+x.i);}
	cp operator - (cp x){return cp(r-x.r,i-x.i);}
	cp operator * (cp x){return cp(r*x.r-i*x.i,r*x.i+i*x.r);}
	void clear(){r=i=0.0;}
}a[maxn],b[maxn],c[maxn],A[maxn];
int n,mx,N=0,L=0,rev[maxn],dig[maxn];
LL s[maxn];

void FFT(cp a[],int flag){
	for (int i=0;i<N;++i) A[i]=a[rev[i]];
	for (int i=0;i<N;++i) a[i]=A[i];
	for (int i=2;i<=N;i<<=1){
		cp wn(cos(2*pi/i),sin(2*pi/i)*flag);
		for (int k=0;k<N;k+=i){
			cp w(1,0);
			for (int j=k;j<k+i/2;++j){
				cp x=a[j],y=a[j+i/2]*w;
				a[j]=x+y; a[j+i/2]=x-y;
				w=w*wn;
			}
		}
	}if (flag==-1) for (int i=0;i<N;++i) a[i].r/=N;
}

int main(){
	int T; scanf("%d",&T);
	while (T--){
		scanf("%d",&n);
		mx=0; memset(s,0,sizeof(s));
		for (int i=1;i<=n;++i)
			{int x;scanf("%d",&x);mx=max(x,mx);s[x]++;}
		for (N=1,L=0;N<mx+1;N<<=1,L++);
		N<<=1; L++; mx++;
		memset(dig,0,sizeof(dig));
		memset(rev,0,sizeof(rev));
		for (int i=0;i<N;++i){
			int len=0; a[i].clear(); b[i].clear();
			for (int t=i;t;t>>=1) dig[len++]=(t&1);
			for (int j=0;j<L;++j) rev[i]=(rev[i]<<1)|(dig[j]);
		}
		for (int i=1;i<=mx;++i)
		    a[i]=cp((double)s[i]),b[i]=cp((double)s[i]);
		FFT(a,1); FFT(b,1);
		for (int i=0;i<N;++i) c[i]=a[i]*b[i];
		FFT(c,-1);
		LL ans=0,tot=1LL*n*(n-1)*(n-2)/6,sum=0;
		for (int i=0;i<=mx;++i){
			sum+=(LL)(c[i].r+0.5);
			if (i%2==0) sum-=s[i/2];
			if (!s[i]) continue;
			ans+=1LL*sum*s[i];
		}
		ans/=2;
		printf("%.7lf\n",1.0-(double)ans/(double)tot);
	}
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值