洛谷 P4721 【模板】分治 FFT 题解

题目传送门

题目大意: 给出 g [ i ] g[i] g[i],求 f [ i ] = ∑ j = 1 i f [ i − j ] g [ i ] f[i]=\sum_{j=1}^i f[i-j]g[i] f[i]=j=1if[ij]g[i],边界为 f [ 0 ] = 1 f[0]=1 f[0]=1

题解

分治 F F T FFT FFT 的做法详见这里。

由于这种带两个 l o g log log 的不优秀做法深不得人心,于是我们来发掘更优秀的做法。

发现这个柿子很像卷积,不妨考虑生成函数,设 F ( x ) = ∑ i = 0 f [ i ] x i , G ( x ) = ∑ i = 0 g [ i ] x i F(x)=\sum\limits_{i=0}f[i]x^i,G(x)=\sum\limits_{i=0} g[i]x^i F(x)=i=0f[i]xi,G(x)=i=0g[i]xi,那么有:
F = F G + f [ 0 ] F = F G + 1 F = 1 1 − G \begin{aligned} F&=FG+f[0]\\ F&=FG+1\\ F&=\frac 1 {1-G} \end{aligned} FFF=FG+f[0]=FG+1=1G1

于是多项式求逆即可。

代码如下:

#include 
#include 
#include 
#include 
using namespace std;
#define ll long long
#define maxn 300010
#define mod 998244353

int n;
ll g[maxn],invg[maxn];
ll ksm(ll x,int y)
{
	ll re=1,tot=x;
	while(y)
	{
		if(y&1)re=re*tot%mod;
		tot=tot*tot%mod;
		y>>=1;
	}
	return re;
}
#define inv(x) ksm(x,mod-2)
int r[maxn],up,l;
void work(int len)
{
	up=1;l=0;
	while(up<=len)up<<=1,l++;
	for(int i=1;i<up;i++)
	r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
}
const ll G=3,invG=inv(G);
void ntt(ll *f,int len,int type)
{
	for(int i=1;i<len;i++)
	if(i<r[i])swap(f[i],f[r[i]]);
	for(int mid=1;mid<len;mid<<=1)
	{
		ll wn=ksm((type==1?G:invG),(mod-1)/mid/2);
		for(int block=mid<<1,j=0;j<len;j+=block)
		{
			ll w=1;
			for(int i=j;i<j+mid;i++,w=w*wn%mod)
			{
				ll x=f[i],y=f[i+mid]*w%mod;
				f[i]=(x+y)%mod;f[i+mid]=(x-y+mod)%mod;
			}
		}
	}
}
ll a[maxn],b[maxn];
void solve(int len,ll *arr,ll *invarr)
{
	if(len==1){invarr[0]=inv(arr[0]);return;}
	solve((len+1)>>1,arr,invarr);
	work(len+n);
	for(int i=0;i<up;i++)
	a[i]=arr[i],b[i]=(i<(len+1)>>1?invarr[i]:0);
	ntt(a,up,1);ntt(b,up,1);
	for(int i=0;i<up;i++)
	a[i]=b[i]*((2-a[i]*b[i]%mod+mod)%mod)%mod;
	ntt(a,up,-1);
	ll invup=inv(up);
	for(int i=0;i<len;i++)
	invarr[i]=a[i]*invup%mod;
}

int main()
{
	scanf("%d",&n);
	for(int i=1;i<n;i++)
	scanf("%lld",&g[i]),g[i]=(mod-g[i])%mod;
	g[0]=1;
	solve(n,g,invg);
	for(int i=0;i<n;i++)
	printf("%lld ",invg[i]);
}

你可能感兴趣的:(题解_杂)