分治卷积

问题

已知$g(i)$的各项函数值

$f(i)=\sum_{j=1}^i g(j)*f(i-j)$

求$f(i)$的各项函数值

解法

考虑cdq分治思想

每次二分,先把左边的f(i)计算出来, 然后计算左边的f(i)对右边的贡献,再继续累积右边的贡献

二分到达边界时,表明这个点的函数值已经统计完毕

同理,当二分完一个区间时,表明该区间所有函数值已计算完毕

举例:

假设一开始知道f(0)的值

二分到区间0~1时,左边区间0~0已知,那么可以用f(0)计算f(1),另外f(1)除了f(0)无其他贡献来源,所以f(1)计算完毕

(绿色表示计算完成,黄色表示正在计算中)

卷积扩展知识_i++

回退到0~2时,0~1已知,可以用于计算f(1)~f(2)

卷积扩展知识_i++_02

 进入2~2,到达边界,f(2)计算完成,回退,累计f(2)对f(3)的贡献

卷积扩展知识_知识讲解_03

 进入3~3,到达边界,f(3)计算完成,回退至0~7区间,累计f(0~3)对f(4~7)的贡献

卷积扩展知识_i++_04

 之后以此类推即可

代码

代码中有些细节解释

#include<bits/stdc++.h>
using namespace std;
#define N 300000
#define int long long
int g[N],f[N],res[N],ind,rev[N],ta[N],tb[N];
const int p=998244353;
int qpow(int aa,int bb)
{
	int res=1;
	aa%=p;
	while(bb)
	{
		if(bb&1) res*=aa,res%=p;
		aa*=aa,aa%=p;
		bb>>=1ll;
	}
	return res;
}
void ntt(int arr[],int g,int n)
{
	for(int i=1;i<=n;i++)
	{
		if(i<rev[i]) swap(arr[i],arr[rev[i]]);
	}
	for(int len=1;len<n;len*=2)
	{
		int offect=qpow(g,(p-1)/(len<<1));
		for(int i=0;i<n;i+=len*2)
		{
			for(int j=0,g1=1;j<len;j++,g1=g1*offect%p)
			{
				int t=arr[i+j];
				arr[i+j]=(t+g1*arr[i+j+len]%p)%p;
				arr[i+j+len]=(t-g1*arr[i+j+len]%p+p)%p;
			}
		}
	}
}
void mul(int ans[],int len)
{
	int x=0,y=1;
	while(y<=len) x++,y<<=1;
	len=y;
	for(int i=0;i<=len;i++) rev[i]=(rev[i>>1]>>1)|((i&1)<<(x-1));
	ntt(ta,3,len);
	ntt(tb,3,len);
	for(int i=0;i<=len;i++) ans[i]=ta[i]*tb[i]%p;
	int inv=qpow(3,p-2);
	ntt(ans,inv,len);
	//ntt(a,inv,len,p);
	//ntt(b,inv,len,p);
	int z=qpow(len,p-2);
	for(int i=0;i<=len;i++) ans[i]=ans[i]*z%p,ta[i]=tb[i]=0;
}
void divide(int l,int r)
{
	if(l==r) return;
	int mid=(l+r)/2;
	divide(l,mid);
	memset(res,0,16*(r-l+1));
	memcpy(ta,f+l,8*(mid-l+1));
	memcpy(tb,g,8*(r-l+1));//实际是f(l~mid)*g(mid+1~r) 但为了凑足g的次数还是从g(1)开始 
	mul(res,r-l+1);//乘出来的res应该是r-l+1+mid-l+1项的,但我们只关心mid+1~r项,所以只需要计算1~r-l+1项就行了 
	for(int i=mid+1;i<=r;i++) f[i]+=res[i-l],f[i]%=p; 
	divide(mid+1,r);
}
signed main()
{
	int n;
	cin>>n;
	n--; 
	for(int i=1;i<=n;i++) scanf("%lld",&g[i]);
	f[0]=1;
	int t=1;
	while(t<n) t<<=1,ind++;
	divide(0,t-1);
	for(int i=0;i<=n;i++) printf("%lld ",f[i]);
}

  

任意模数卷积

如果题目的模数不是NTT模数,甚至没有模数,并且值域范围很大,fft会掉精度

介绍两种办法

拆系数fft

将多项式系数拆为$a_i=b_i*m+c_I$,m是阈值,一般取1e5,这样如果$a_i<=10^9,则b_i,c_i<=10^5$,乘起来不会太大

这样$f(x)=f_1(x)*m+f_2(x)$

然后$f(x)*g(x)=f_1(x)*g_1(x)*m^2+(f_1(x)*g_2(x)+f_2(x)*g_1(x))*m+f_2(x)*g_2(x)$

做四次fft即可

三模数ntt

卷积扩展知识_知识讲解_05

 代码

#include<bits/stdc++.h>
using namespace std;
#define N 300000
#define int long long
int ta[N],tb[N],a[N],b[N],ans[5][N],p[4]={0,469762049,998244353,1004535809},rev[N];
int fmul(int a, int b, int mod) {//用于计算会爆long long的乘法
    a %= mod, b %= mod;
    return ((a * b - (int)((int)((long double)a / mod * b + 1e-3) * mod)) % mod + mod) % mod;
}
int qpow(int aa,int bb,int pp)
{
	int res=1;
	aa%=pp;
	while(bb)
	{
		if(bb&1) res*=aa,res%=pp;
		aa*=aa,aa%=pp;
		bb>>=1ll;
	}
	return res;
}
void ntt(int arr[],int g,int n,int p)
{
	for(int i=1;i<=n;i++)
	{
		if(i<rev[i]) swap(arr[i],arr[rev[i]]);
	}
	for(int len=1;len<n;len*=2)
	{
		int offect=qpow(g,(p-1)/(len<<1),p);
		for(int i=0;i<n;i+=len*2)
		{
			for(int j=0,g1=1;j<len;j++,g1=g1*offect%p)
			{
				int t=arr[i+j];
				arr[i+j]=(t+g1*arr[i+j+len]%p)%p;
				arr[i+j+len]=(t-g1*arr[i+j+len]%p+p)%p;
			}
		}
	}
}
int len=1,l=0;
void mul(int a[],int b[],int ans[],int n,int p)
{
	
	for(int i=0;i<=len;i++)
	{
		rev[i]=(rev[i>>1]>>1)|((i&1)<<(l-1));
	}
	ntt(a,3,len,p);
	ntt(b,3,len,p);
	for(int i=0;i<=len;i++) ans[i]=a[i]*b[i]%p;
	int inv=qpow(3,p-2,p);
	ntt(ans,inv,len,p);
	//ntt(a,inv,len,p);
	//ntt(b,inv,len,p);
	for(int i=0;i<=len;i++) ans[i]=ans[i]*qpow(len,p-2,p)%p;
}
signed main()
{
	int n,m,p0;
	cin>>n>>m>>p0;
	while(len<n+m+1) len<<=1,l++;
	for(int i=0;i<=n;i++) scanf("%lld",&a[i]);
	for(int i=0;i<=m;i++) scanf("%lld",&b[i]);
	for(int i=1;i<=3;i++)
	{
		//memset(ta,0,sizeof(ta));
		//memset(tb,0,sizeof(tb));
		for(int j=0;j<=len;j++) ta[j]=a[j];
		for(int j=0;j<=len;j++) tb[j]=b[j];
		mul(ta,tb,ans[i],n+m+1,p[i]);
	}
	int pn=p[1]*p[2],inv1=qpow(p[2],p[1]-2,p[1]),inv2=qpow(p[1],p[2]-2,p[2]),inv3=qpow(pn,p[3]-2,p[3]);
	for(int i=0;i<=n+m;i++)
	{
		ans[4][i]=(fmul(ans[1][i]*p[2],inv1,pn)+fmul(ans[2][i]*p[1],inv2,pn))%pn;
		int t=(ans[3][i]-ans[4][i]%p[3]+p[3])%p[3]*inv3%p[3];
		ans[0][i]=(pn%p0*t%p0+ans[4][i])%p0;
		printf("%lld ",(ans[0][i]+p0)%p0);
	}

}