多项式题目

一些符号与约定

大写字母代表多项式,例如(F)(G)

(f_i) 则代表多项式(F)(x^i) 项的系数

SPOJ-SWERC14C, Golf Bot

description

有一个长为 (N) 的数组 (k) 与一个长为 (M) 的数组(d) ,求出 (d) 数组中能被 (le 2)(k) 数组中的数相加表示出来的数有多少个。((N,M,d_i,k_ile 2 imes 10^5))

solution

同底数幂的乘法是指数相加系数相乘。这启示我们将(k) 放到指数上面。具体地,构造多项式(F)(f_i=1) 当且仅当(i) 出现在(k) 数组中。注意到不一定是由恰好两个数相加得到的,于是我们可以添加(f_0=1) 表示不选择。令(G=F^2) ,不难发现(g_i>0)(i) 能被表示出来是充分必要条件。于是直接(FFT) 即可。

code

#include<bits/stdc++.h>
using namespace std;
typedef double db;
const int N=8e5+5;const db pi=acos(-1.0),eps=1e-10;
struct cp{db x,y;};
inline cp operator+(const cp&x,const cp&y){return {x.x+y.x,x.y+y.y};}
inline cp operator-(const cp&x,const cp&y){return {x.x-y.x,x.y-y.y};}
inline cp operator*(const cp&x,const cp&y){return {x.x*y.x-x.y*y.y,x.y*y.x+x.x*y.y};}
namespace Basis
{
	int rev[N];cp wn[N],f[N],g[N];
	inline void pre(int l,int lim)
	{
		for(int i=0;i<lim;++i)
			rev[i]=(rev[i>>1]>>1)|((i&1)<<(l-1));
	}
	inline void FFT(cp*f,int lim,bool tp)
	{
		for(int i=0;i<lim;++i)if(i<rev[i])swap(f[i],f[rev[i]]);
		for(int mid=1;mid<lim;mid<<=1)
		{
			wn[0]={1,0};cp g={cos(pi/mid),(tp?-1:1)*sin(pi/mid)};
			for(int i=1;i<mid;++i)wn[i]=wn[i-1]*g;
			int len=mid<<1;
			for(int j=0;j<lim;j+=len)
				for(int k=0;k<mid;++k)
				{
					cp x=f[j+k],y=f[j+mid+k]*wn[k];
					f[j+k]=x+y,f[j+mid+k]=x-y;
				}
		}
		if(tp)for(int i=0;i<lim;++i)f[i].x/=lim;
	}
	inline void mul(int n,int m,int*a,int*b)
	{
		int l=0,lim=1;while(lim<n+m)lim<<=1,++l;pre(l,lim);
		for(int i=0;i<lim;++i)
			f[i]={i<n?a[i]:0,0},g[i]={i<m?b[i]:0,0};
		FFT(f,lim,0),FFT(g,lim,0);
		for(int i=0;i<lim;++i)f[i]=f[i]*g[i];
		FFT(f,lim,1);
	}
}
int n,m,a[N],rub[N],cnt,mx,ans;
int main()
{
	while(scanf("%d",&n)==1)
	{
		ans=mx=cnt=0;
		for(int i=1,k;i<=n;++i)
			scanf("%d",&k),a[k]=1,mx=max(rub[++cnt]=k,mx);
		rub[++cnt]=0,a[0]=1;Basis::mul(mx+1,mx+1,a,a);
		scanf("%d",&m);
		for(int i=1,d;i<=m;++i)
			scanf("%d",&d),ans+=d>2*mx?0:(Basis::f[d].x>eps);
		printf("%d
",ans);
		for(int i=1;i<=cnt;++i)a[rub[i]]=0;
	}
	return 0;
}

UVA12298 Super Poker II

solution

考虑(n=a_1+a_2+a_3+a_4) 不难发现相当于从每种花色的牌中选出一种直接组合,这和多项式乘法的意义相同。

于是类似上一道题,我们构造(F(x)=sum_limits i[i为合数]x^i) 。如果某些牌不能选择,那么我们钦定其对应系数为(0) 。而后我们将四种花色代表的多项式乘起来得到多项式(G)(g_i) 就表示(n=i) 时的方案数,直接输出即可。

注意此题要开long double 。

code

#include<bits/stdc++.h>
using namespace std;
typedef long double db;
typedef long long ll;
const int N=1000000+5;const db pi=acos(-1.0),eps=1e-10;
struct cp{db x,y;};
inline cp operator+(const cp&x,const cp&y){return {x.x+y.x,x.y+y.y};}
inline cp operator-(const cp&x,const cp&y){return {x.x-y.x,x.y-y.y};}
inline cp operator*(const cp&x,const cp&y){return {x.x*y.x-x.y*y.y,x.y*y.x+x.x*y.y};}
namespace Basis
{
	int rev[N];cp wn[N],f[N],g[N];
	inline void pre(int l,int lim)
	{
		for(int i=0;i<lim;++i)
			rev[i]=(rev[i>>1]>>1)|((i&1)<<(l-1));
	}
	inline void FFT(cp*f,int lim,bool tp)
	{
		for(int i=0;i<lim;++i)if(i<rev[i])swap(f[i],f[rev[i]]);
		for(int mid=1;mid<lim;mid<<=1)
		{
			wn[0]={1,0};cp g={cos(pi/mid),(tp?-1:1)*sin(pi/mid)};
			for(int i=1;i<mid;++i)wn[i]=wn[i-1]*g;
			int len=mid<<1;
			for(int j=0;j<lim;j+=len)
				for(int k=0;k<mid;++k)
				{
					cp x=f[j+k],y=f[j+mid+k]*wn[k];
					f[j+k]=x+y,f[j+mid+k]=x-y;
				}
		}
		if(tp)for(int i=0;i<lim;++i)f[i].x/=lim;
	}
	inline void mul(int n,int m,ll*a,ll*b,ll*ans)
	{
		int l=0,lim=1;while(lim<n+m)lim<<=1,++l;pre(l,lim);
		for(int i=0;i<lim;++i)
			f[i]={i<n?a[i]:0,0},g[i]={i<m?b[i]:0,0};
		FFT(f,lim,0),FFT(g,lim,0);
		for(int i=0;i<lim;++i)f[i]=f[i]*g[i];
		FFT(f,lim,1);
		for(int i=0;i<lim;++i)ans[i]=(ll)(f[i].x+0.1);
	}
}
ll a[N],b[N],c[N],d[N],h[N];
bool flag[N];int pr[N],pcnt,n,m,k;
inline void init(int mx)
{
	flag[1]=1;
	for(int i=2;i<=mx;++i)
	{
		if(!flag[i])pr[++pcnt]=i;
		for(int j=1;j<=pcnt;++j)
		{
			int num=pr[j]*i;if(num>mx)break;
			flag[num]=1;if(i%pr[j]==0)break;
		}
	}
}
inline int get(char*ch,int len)
{
	int s=0;
	for(int i=0;i<len-1;++i)s=s*10+ch[i]-'0';
	return s;
}
int main()
{
	init(50000);
	char opt[10];
	while(scanf("%d%d%d",&n,&m,&k)&&n&&m)
	{
		for(int i=2;i<=m;++i)if(flag[i])a[i]=b[i]=c[i]=d[i]=1;
		for(int i=1;i<=k;++i)
		{
			scanf("%s",opt);
			int len=strlen(opt),num=get(opt,len);char t=opt[len-1];
			if(t=='S')a[num]=0;
			else if(t=='H')b[num]=0;
			else if(t=='C')c[num]=0;
			else d[num]=0;
		}
		Basis::mul(m+1,m+1,a,b,h);
		Basis::mul(m+1,m+1,h,c,h);
		Basis::mul(m+1,m+1,h,d,h);
		for(int i=n;i<=m;++i)printf("%lld
",h[i]);
		puts("");
	}
	return 0;
}

UVA1718 Tile Cutting

solution

情况如图

其中橙色平行四边形的面积为((a+b)(c+d)-ad-bc=ac+bd=s)

注意到(s) 是定值,因此相当于我们要求(a,b,c,d) 满足(ac+bd=s) 的方案数。

可以对于每个(i) 求出(t_i=sum_limits{x,y}[xy=s]) 。后面的就和前面的题相同了。构造多项式(F(x)=sum_limits it_ix^i) ,则(G=F^2) 的各项系数就分别对应该次数的方案数了。

Arithmetic Progressions, CodeChef COUNTARI(bzoj3509)

description

给定一个长度为(n) 的数组(a) ,求有多少个(i,j,k) 满足(i<j<k)(a_k-a_j=a_j-a_i)

data range

(nle 10^5,a_ile 30000)

solution

原式可以化为

[2a_j=a_i+a_k ]

于是可以考虑枚举(j) ,然后统计满足(i<j<k) 和上述条件的((i,k)) 数量。

不难想到多项式,设在(j) 左边数值(t) 出现了(c_t) 次,那么左侧的多项式为

[f(x)=sum_{i=0}^{30000}c_ix^i ]

右侧同理。左右两侧的多项式卷积起来后(x^{2a_j}) 的系数就是答案。

但是每次都这样乘复杂度显然爆炸,考虑优化。

运用分块的思想平衡复杂度,设块大小为(B)

对于每个块,都要进行一次如上的卷积,这部分的复杂度为(mathcal O(frac nBmxlog_2mx)) 其中(mx=30000)

然后我们需要遍历块中每一个下标作为(j) ,然后累加(j) 的所有块内贡献(块外贡献已经算出),这部分的复杂度为(mathcal O(nB))

根据均值不等式,当(B=sqrt{mxlog_2mx}) 时最优,实际的话再微调就可以过了。

code

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e5+5;
typedef double db;
const db pi=acos(-1.0);
int mx;
struct cp{db x,y;};
inline cp operator+(const cp&x,const cp&y){return {x.x+y.x,x.y+y.y};}
inline cp operator-(const cp&x,const cp&y){return {x.x-y.x,x.y-y.y};}
inline cp operator*(const cp&x,const cp&y){return {x.x*y.x-x.y*y.y,x.y*y.x+x.x*y.y};}
namespace Basis
{
	int rev[N<<2],lim=1,l;cp wn[N<<2],a[N<<2],b[N<<2];ll t[N<<2];
	inline void pre(int lim,int l)
	{
		for(int i=0;i<lim;++i)
			rev[i]=(rev[i>>1]>>1)|((i&1)<<(l-1));
	}
	inline void fft(int lim,cp*f,bool tp)
	{
		for(int i=0;i<lim;++i)
			if(i<rev[i])swap(f[i],f[rev[i]]);
		for(int mid=1;mid<lim;mid<<=1)
		{
			wn[0]={1,0};cp g={cos(pi/mid),(tp?-1:1)*sin(pi/mid)};
			for(int i=1;i<mid;++i)wn[i]=wn[i-1]*g;
			int len=mid<<1;
			for(int j=0;j<lim;j+=len)
				for(int k=0;k<mid;++k)
				{
					cp x=f[j+k],y=f[j+mid+k]*wn[k];
					f[j+k]=x+y,f[j+mid+k]=x-y;
				}
		}
	}
	inline void mul(int n,int m,int*f,int*g)
	{
		for(int i=0;i<lim;++i)
			a[i].x=i<n?f[i]:0,b[i].x=i<m?g[i]:0,a[i].y=b[i].y=0;
		fft(lim,a,0),fft(lim,b,0);
		for(int i=0;i<lim;++i)a[i]=a[i]*b[i];
		fft(lim,a,1);
		for(int i=0;i<lim;++i)t[i]=(ll)(a[i].x/lim+0.1);
	}
	inline ll query(int pos){return t[pos];}
}
using namespace Basis;
int n,s[N],blk[N],lb[N],rb[N],cnt[N],f[N],g[N];ll ans;
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;++i)scanf("%d",s+i),mx=max(mx,s[i]);
	int k=sqrt(n*log(mx)*0.9)+1;++mx;
	for(int i=1;i<=n;++i)blk[i]=(i-1)/k+1,++g[s[i]];
	for(int i=1;i<=blk[n];++i)
		lb[i]=rb[i-1]+1,rb[i]=min(k*i,n);
	while(lim<mx+mx)lim<<=1,++l;pre(lim,l);
	for(int i=1;i<=blk[n];++i)
	{
		for(int j=lb[i];j<=rb[i];++j)--g[s[j]],++cnt[s[j]];
		mul(mx,mx,f,g);
		for(int j=lb[i];j<=rb[i];++j)
		{
			int now=s[j]<<1;
			ans+=query(now);--cnt[s[j]];
			for(int o=lb[i];o<j;++o)
				if(now>=s[o])ans+=cnt[now-s[o]]+g[now-s[o]];
			for(int o=j+1;o<=rb[i];++o)
				if(now>=s[o])ans+=f[now-s[o]];
		}
		for(int j=lb[i];j<=rb[i];++j)++f[s[j]];
	}
	printf("%lld
",ans);
	return 0;
}

Unequalled Consumption, NWERC 2005, POJ2764

非常牛逼的一道题目呢。

solution

每种糖果的(OGF)

[A_t=sum_ix^{w_ti}=frac 1{1-x^{w_t}} ]

因此所有糖果合在一起的(OGF)

[F=prod_t^nA_t=prod_t^nfrac 1{1-x^{w_t}} ]

(f_i) 就表示要求总价值为(i) 时的糖果选取方案数。

但是这个东西是个分式,直接不好做。考虑化开,令(W=lcm(w_1,w_2,cdots,w_t)) ,则

[F=frac 1{(1-x^W)^n}prod_t^nfrac{1-x^W}{1-x^{w_t}} ]

由等比数列求和公式(sum_limits {i=l}^rx^i=frac{1-x^{r-l+1}}{1-x}) ,因此

[F=(sum_{i=0}x^{iW})^n imesprod_t^n(sum_{i=0}^{frac{W}{w_t}-1}x^{iw_t}) ]

分为左右两半考虑。右边是一个多重背包:有(n) 种物品,每种物品价值为(w_t) 且有(frac{W}{{w_t}}-1) 个。这个直接暴力做就可以了。设这个多项式为(P) ,其(x^r) 的系数为(p_r) .

再考虑左边的式子,其实只有(x^0,x^W,x^{2W},cdots) 这些位置系数是非(0) 的。而不难发现(x^{iW})其系数为(inom {n+i-1}{n-1}) ,证明可以考虑隔板法,相当于将(i) 分到(n) 个盒子中去的方案数。设这个多项式为(Q)

现在假设我们要求总价值为(m) 时的方案数。不妨令(m=kW+r) ,那么我们有

[G(k,r)=sum_{i=0}^{n-1}q_{(k-i)W} imes p_{iW+r}\ =sum_{i=0}^{n-1}inom{n+k-i-1}{n-1}p_{iW+r} ]

上限为(n-1) 是因为(P) 的次数限制。

回到原问题,我们要求最小的(m=kW+r) 满足(G(k,r)ge P) 。我们可以将(m) 按照其除以(W) 的余数进行分类,即我们每次固定(r) ,考虑所有除以(W)(r)(m) 的贡献。注意到此时(G(k,r)) 是随着(k) 增大而单调递增的,因此我们可以直接二分(可以从(G(k+1,r)-G(k,r)>0) 考虑证明这一结论)。至此问题解决。

NO PAIN NO GAIN
原文地址:https://www.cnblogs.com/zmyzmy/p/14672595.html