BZOJ 3771 Triple

想了我好久,然后发现我好像还是没有学到FFT的精髓……
万物皆可FFT!

由于是权限题,我放一下大意:
给出一个数列,让你从中间按顺序挑出1~3个数,求它们的和可能是多少,并且从小到大输出每种可能的方案数。

数学语言表达:
给出一个有 n n n个数的数列 A A A,求集合
{ A i , A i + A j , A i + A j + A k ∣ 1 ≤ i < j < k ≤ n } \{A_i,A_i+A_j,A_i+A_j+A_k\quad|\quad1\le i<j<k\le n\} {Ai,Ai+Aj,Ai+Aj+Ak1i<j<kn}
并对于每个数,输出组成它的可能方案数

贴个样例就完事儿了

样例输入:

4 
4 
5 
6 
8 

样例输出:

4 1
5 1
6 1
8 1
9 1
10 1
11 1
12 1
13 1
14 1
15 1
17 1
18 1
19 1

记得之前OZY师兄给我们讲生成函数,就讲了类似的一道题
一眼看下去是个背包嘛,但是数据范围大而且有一些奇奇怪怪的限制,还要输出方案数
不管怎么样先打个暴力再说嘛
首先抛开大数据和个数的限制范围,我们来看固定使用两个物品的解决方案,并且包括重复使用一个物品的情况
转移方程就是这样的啦:
F k = ∑ i = 1 n ∑ j = i n ( A i + A j = k ) ? 1 : 0 F_k=\sum\limits_{i=1}^{n}\sum\limits_{j=i}^{n}\quad(A_i+A_j=k)?1:0 Fk=i=1nj=in(Ai+Aj=k)?1:0
我们再回顾一下卷积:
F k = ∑ i = 1 k A i B k − i F_k=\sum\limits_{i=1}^{k}A_iB_{k-i} Fk=i=1kAiBki
乱搞一下可以变成这样:
F k = ∑ i = 1 n ∑ j = i n ( i + j = k ) ? A i B j : 0 F_k=\sum\limits_{i=1}^{n}\sum\limits_{j=i}^{n}\quad(i+j=k)?A_iB_j:0 Fk=i=1nj=in(i+j=k)?AiBj:0
强行转化成卷积啦

实际上我们可以按照这个思路去做啊:
建立一个多项式 G ( x ) G(x) G(x),使 ∀ i ∈ [ 1 , n ] \forall i\in [1,n] i[1,n] G A i + + G_{A_i}++ GAi++(相当于背包中 f [ a [ i ] ] + + f[a[i]]++ f[a[i]]++
G G G自己跟自己卷一下(卷积讲起来好捞啊,其实就是求一个平方啦),求解过程 G A i + A j + = G A i ∗ G A j G_{A_i+A_j}+=G_{A_i}*G_{A_j} GAi+Aj+=GAiGAj g [ a [ i ] + a [ j ] ] + = f [ a [ i ] ] ∗ f [ a [ j ] ] g[a[i]+a[j]]+=f[a[i]]*f[a[j]] g[a[i]+a[j]]+=f[a[i]]f[a[j]]对应
写一个表出来可能更好理解:

对于本问题中使用两个物品的部分
如果允许出现 i = j i=j i=j并且 ( i , j ) (i,j) (i,j) ( j , i ) (j,i) (j,i)不等价
设数列 A A A 1 , 2 , 4 1,2,4 1,2,4
暴力中的 f f f如下表:

0123456789
0110100000

求解答案表 g g g的过程如下:

01( A i A_i Ai)( A j A_j Aj)2( A i + A j A_i+A_j Ai+Aj)3456789
f f f0110100000
g g g0010000000
01( A i A_i Ai)2( A j A_j Aj)3( A i + A j A_i+A_j Ai+Aj)456789
f f f0110100000
g g g0011000000
01( A i A_i Ai)234( A j A_j Aj)5( A i + A j A_i+A_j Ai+Aj)6789
f f f0110100000
g g g0011010000
01( A j A_j Aj)2( A i A_i Ai)3( A i + A j A_i+A_j Ai+Aj)456789
f f f0110100000
g g g0012010000
012( A i A_i Ai)( A j A_j Aj)34( A i + A j A_i+A_j Ai+Aj)56789
f f f0110100000
g g g0012110000
012( A i A_i Ai)34( A j A_j Aj)56( A i + A j A_i+A_j Ai+Aj)789
f f f0110100000
g g g0012111000

……直到最后, g = { 1 , 0 , 2 , 2 , 1 , 2 , 1 , 0 , 0 } g=\{1,0,2,2,1,2,1,0,0\} g={1,0,2,2,1,2,1,0,0}
恰是我们卷积时进行的一模一样的步骤,一模一样的结果

扯了这么多,终于可以用FFT对它进行加速了
对于3个物品的情况,我们只需要求 G G G的三次方即可。

接下来就是解决重复的问题了
如果 D D D是答案,在不解决重复问题的情况下 D = G 3 + G 2 + G D=G^3+G^2+G D=G3+G2+G
为了解决重复问题,我们引入 H ( x ) H(x) H(x) I ( x ) I(x) I(x)
其中, G i = H 2 i = I 3 i G_i=H_{2i}=I_{3i} Gi=H2i=I3i
H , I H,I H,I分别代表重复取两个相同数和三个相同数的情况。
当取一个数时,没有重复的情况。
当取两个数时,我们为消除 i = j i=j i=j的情况减去一个 H H H,为了消除 j < i j<i j<i的情况除以2
当取三个数时,我们减去 3 G H 3GH 3GH,分别对应 { i , j , j } , { j , i , j } , { j , j , i } \{i,j,j\},\{j,i,j\},\{j,j,i\} {i,j,j},{j,i,j},{j,j,i}三种情况,但是其中包括了 i = j i=j i=j,即三个数相等的情况,于是加回两个 I I I,再为了消除排列先后顺序带来的多余项除以6

大概流程如下:

  1. G A i + + , H 2 A i + + , I 3 A i + + G_{A_i}++,H_{2A_i}++,I_{3A_i}++ GAi++,H2Ai++,I3Ai++
  2. G , H , I G,H,I G,H,I进行DFT变换
  3. D = G i + G i 2 − H i 2 + G i 3 − 3 G H + 2 I 6 D=G_i+\frac{{G_i}^2-H_i}{2}+\frac{{G_i}^3-3GH+2I}{6} D=Gi+2Gi2Hi+6Gi33GH+2I
  4. D D D进行IDFT变换
  5. 检测D的各项,若不为零则输出

代码如下:

#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
const double pi=acos(-1);
struct cp
{
	double x,i;
	cp(double _x=0,double _i=0):x(_x),i(_i) {}
	cp(const cp &b):x(b.x),i(b.i) {}

	cp operator +(cp b)const{return cp(x+b.x,i+b.i);}
	cp operator -(cp b)const{return cp(x-b.x,i-b.i);}
	cp operator *(cp b)const{return cp(x*b.x-i*b.i,x*b.i+i*b.x);}

	cp operator +(double b)const{return (*this)+cp(b);}
	cp operator -(double b)const{return (*this)-cp(b);}
	cp operator *(double b)const{return (*this)*cp(b);}

	cp& operator *=(cp b){return (*this)=(*this)*b;}
};//复数
cp operator *(int a,cp b)
{return b*a;}
int R[1<<17];
int x[41000];
cp a[1<<17|1],b[1<<17|1],c[1<<17|1],ans[1<<17|1];
void fft(cp arr[],int len,int inv)
{
	for(int n=2;n<=len;n<<=1)
	{
		const cp ur(cos(2*pi/n),sin(2*inv*pi/n));
		const int mid=n>>1;
		for(cp *a=arr;a<arr+len-1;a+=n)
		{
			cp r(1),t;
			for(int i=0;i<mid;i++,r*=ur)//被迫从0开始————存在等于零的情况
			{
				t=a[i+mid]*r;
				a[i+mid]=a[i]-t;
				a[i]=a[i]+t;
			}
		}
	}
}
long long int k[1<<17];
int main()
{
	int n,m=0;scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",x+i);
		a[x[i]].x++;
		b[2*x[i]].x++;
		c[3*x[i]].x++;
		m=max(m,3*x[i]);
	}
	for(n=1;n<=m;n<<=1);
	for(int i=1;i<n;i++)
		R[i]=R[i>>1]>>1|(i&1?n>>1:0);

	for(int i=0;i<n;i++)
		if(i<R[i])swap(a[i],a[R[i]]);
	fft(a,n,1);
	for(int i=0;i<n;i++)
		if(i<R[i])swap(b[i],b[R[i]]);
	fft(b,n,1);
	for(int i=0;i<n;i++)
		if(i<R[i])swap(c[i],c[R[i]]);
	fft(c,n,1);

	for(int i=0;i<=n;i++)
		ans[i]=a[i]+(a[i]*a[i]-b[i])*(1.0/2)+(a[i]*a[i]*a[i]-3.0*a[i]*b[i]+2.0*c[i])*(1.0/6);

	for(int i=0;i<=n;i++)
		if(i<R[i])swap(ans[i],ans[R[i]]);
	fft(ans,n,-1);
	for(int i=0;i<=n;i++)
	{
		k[i]=ans[i].x/n+0.5;
		if(k[i])printf("%d %lld\n",i,k[i]);
	}
	return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值