SPOJ8372 TSUM - Triple Sums 生成函数 FFT 容斥

11 篇文章 0 订阅
6 篇文章 0 订阅

题目链接

题意:
给你n个整数,求从其中任选3个所有可能组成的数是哪些和能组成的这些数分别有多少种可能的组合,我们认为相同的3个数的不同排列是同一种,每个数只能用一次。

题解:
假如不考虑每个数只能用一次这个限制,并且也先不管不同排列算作同一种,这个不同排列只要最后除一个排列数就好了。那么我们只需要构造一个生成函数 A ( x ) A(x) A(x) A ( x ) = a 1 x + a 2 x 2 + . . . + a n x n A(x)=a_1x+a_2x^2+...+a_nx^n A(x)=a1x+a2x2+...+anxn,其中 a n a_n an表示这 n n n个数中有几个数的值是 n n n,答案是 A ∗ A ∗ A A*A*A AAA

下面我们考虑同一个数会被多次用到的方案数,用一点容斥的思想来算这个数。我们先减去三个中有两个数是用了同一个数的方案数,我们设为 B B B函数, B = a 1 x 2 + a 2 x 4 + . . . + a n x 2 n B=a_1x^2+a_2x^4+...+a_nx^{2n} B=a1x2+a2x4+...+anx2n。我们用 A ∗ B A*B AB即可求出有两个数相同的方案数,其中包含了只有两个数相同和三个数全相同的情况。由于我们先不考虑不同的排列是同一种的情况,于是我们这里先乘上一个 C 3 2 C_{3}^2 C32,但是我们发现其实我们把三个数全相同的多减了两次,于是要加上两倍的三个全相同的情况。我们构造 C C C函数为三个数全相同的方案数, C = a 1 x 3 + a 2 x 6 + . . . + a n x 3 n C=a_1x^3+a_2x^6+...+a_nx^{3n} C=a1x3+a2x6+...+anx3n。最后我们还要除以一个3个数的全排列6。于是答案就是 A ∗ A ∗ A − 3 ∗ A ∗ B + 2 ∗ C 6 \frac{A*A*A-3*A*B+2*C}{6} 6AAA3AB+2C。其中的多项式乘法用FFT优化。

我们发现有负数,那么我们把负数集体加上20000即可。由于我们最后乘了3次方,所以答案的下标要减60000。

代码:

#include <bits/stdc++.h>
using namespace std;

int n,m,l,rev[3000010];
complex<double> a[3000010],b[3000010],c[3000010];
const double pi=acos(-1);
inline void fft(complex<double> *a,int dft)
{
	for(int i=0;i<n;++i)
	{
		if(i<rev[i])
		swap(a[i],a[rev[i]]);
	}
	for(int i=1;i<n;i<<=1)
	{
		complex<double> wn(cos(pi/i),dft*sin(pi/i));
		for(int p=i<<1,j=0;j<n;j+=p)
		{
			complex<double> w(1,0);
			for(int k=0;k<i;++k)
			{
				complex<double> x=a[j+k],y=w*a[i+j+k];
				a[j+k]=x+y;
				a[i+j+k]=x-y;
				w=w*wn;
			}
		}
	}
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;++i)
	{
		int x;
		scanf("%d",&x);
		a[x+20000]+=(1);
		b[2*(x+20000)]+=(1);
		c[3*(x+20000)]+=(1);
		m=max(m,3*(x+20000));
	}
	m++;
	m<<=1;
	for(n=1;n<=m;n<<=1)
	++l;
	for(int i=0;i<n;++i)
	rev[i]=(rev[i>>1]>>1)|((i&1)<<(l-1));
	fft(a,1);
	fft(b,1);
	fft(c,1);
	complex<double> ji1=(6),ji2=(3),ji3=(2);
	for(int i=0;i<n;++i)
	a[i]=(a[i]*a[i]*a[i]-ji2*a[i]*b[i]+ji3*c[i])/ji1;
	fft(a,-1);
	for(int i=0;i<m;++i)
	{
		long long ji=(long long)(a[i].real()/n+0.5);
		if(ji>0)
		printf("%d : %lld\n",i-60000,ji);
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值