BZOJ3771: Triple

14 篇文章 0 订阅
14 篇文章 0 订阅

题目大意:给定n把斧头的价值,问在这些斧头中随便挑一个两个或三个,价值总和分别有多少种


拿这道题练了练FFT的理解程度

首先我们考虑一个函数F(x)=a0+a1x^1+a2x^2+........无穷无尽(当然到后面系数可能是0)

我们称这个为生成函数

接着我们可以把每一项的系数想象成这种斧头的数量,比如当你有一把1斧头,一把3斧头,生成函数就是

F(x)=x+x^3

这个东西有什么好处呢?我们发现当取一把斧头的时候,每项系数就是这种方案对应的答案

而如果我们把这个函数平方一下,就是取两把斧头(不考虑重复)时的答案

立方一下就得到了三把斧头的答案

这一步用FFT只需要O(NlogN)的复杂度!

然后就是去重,我们发现,将原来多项式的每一项系数挪到平方项的位置就正好是去两个斧头重复的方案,例如上面那个例子

F(x)平方之后得到了F(x)^2=x^6+2*x^4+x^2

将F(x)挪动系数之后就变成了G(x)=x^2+x^6

二者相减再除以二就是选取两把斧头不重复的方案数

稍稍复杂一些,我们也可以推出三把斧头不重复的方案数,具体看代码


 #include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define N 131072*4+10
using namespace std;
const double pi=acos(-1);
struct E{double s,x;};
E operator-(const E&x,const E&y){return (E){x.s-y.s,x.x-y.x};}
E operator+(const E&x,const E&y){return (E){x.s+y.s,x.x+y.x};}
E operator*(const E&x,const E&y){return (E){x.s*y.s-x.x*y.x,x.s*y.x+x.x*y.s};}
E omg[N];
void FFT(E *A,E *B,int s,int le,int t)
{
	if(le==1){B[s]=A[s];return;}
	int l=le/2,i;
	for(i=0;i<l;i++)
	B[s+i]=A[s+2*i],B[s+l+i]=A[s+2*i+1];
	FFT(B,A,s,l,t*2);
	FFT(B,A,s+l,l,t*2);
	for(i=0;i<l;i++)
	{
		B[s+i]=A[s+i]+omg[i*t]*A[s+i+l];
		B[s+l+i]=A[s+i]-omg[i*t]*A[s+i+l];
	}
}
E ffta[N],fftb[N],fftc[N];
void AFFT(int *A,int *B,int *C,int n)
{
	int i=1;
	while(i<n) i*=2;
	n=2*i;
	for(i=0;i<=n;i++)
	{
		ffta[i]=(E){A[i],0};
		fftb[i]=(E){B[i],0};
		omg[i]=(E){cos(pi*2*i/n),sin(pi*2*i/n)};
	}
	FFT(ffta,fftc,0,n,1);
	FFT(fftb,ffta,0,n,1);
	for(i=0;i<n;i++)
	fftb[i]=ffta[i]*fftc[i];
	for(i=0;i<=n;i++)
	omg[i]=(E){cos(-pi*2*i/n),sin(-pi*2*i/n)};
	FFT(fftb,fftc,0,n,1);
	for(i=0;i<n;i++)
	C[i]=(int)(fftc[i].s/n+0.5);
}
int A[N],B[N],C[N];
int ans[N],tmp[N],tmp2[N],tmp3[N];
int main()
{
	/*freopen("triple8.in","r",stdin);
	freopen("triple8.ans","w",stdout);*/
	int n;
	scanf("%d",&n);
	int i,j,x;
	for(i=1;i<=n;i++)
	{
		scanf("%d",&x);
		A[x]=1;
		B[2*x]=1;
		C[3*x]=1;
	}
	AFFT(A,B,tmp3,40000);
	AFFT(A,A,tmp,80000);
	AFFT(A,tmp,tmp2,120000);
	for(i=0;i<=120000;i++)
	{
		ans[i]=(tmp2[i]-3*tmp3[i]+2*C[i])/6+(tmp[i]-B[i])/2+A[i];
		if(ans[i]>0) printf("%d %d\n",i,ans[i]);
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值