【luoguP4721】分治 FFT

该博客介绍了一种使用分治策略和快速傅里叶变换(FFT)来解决数组计算问题的方法。通过描述如何将问题分解并计算子区间贡献,作者展示了如何处理特定的数组更新。博客中还提到,由于问题的特性,当区间大小减小时,可以直接返回结果,避免了复杂计算。作者设置了学习多项式逆元以进一步优化此类问题的计划。

description

给定长度为 n − 1 n-1 n1的数组 g [ 1 ] , g [ 2 ] , . . , g [ n − 1 ] g[1],g[2],..,g[n-1] g[1],g[2],..,g[n1],求 f [ 0 ] , f [ 1 ] , . . , f [ n − 1 ] f[0],f[1],..,f[n-1] f[0],f[1],..,f[n1],其中

f [ i ] = ∑ j = 1 i f [ i − j ] g [ j ] f[i]=\sum_{j=1}^if[i-j]g[j] f[i]=j=1if[ij]g[j]

边界为 f [ 0 ] = 1 f[0]=1 f[0]=1。答案模 998244353 998244353 998244353


analysis

  • 一道分治 N T T NTT NTT板题

  • 经历过城市规划那题的洗礼之后这题变得微不足道

  • 考虑 C D Q CDQ CDQ分治,求出 [ l , m i d ] [l,mid] [l,mid] [ m i d + r ] [mid+r] [mid+r]的贡献

  • f [ l , m i d ] f[l,mid] f[l,mid]拉出来,与 g [ 1.. r − l ] g[1..r-l] g[1..rl]相乘,答案数组的后 r − m i d r-mid rmid位就是分别对 [ m i d + r ] [mid+r] [mid+r]的贡献

  • 具体可以画出两个多项式在分治过程中的相乘,结合每一个 f f f的值就可以弄清楚

  • 由于这个 N T T NTT NTT很清真所以 l = = r l==r l==r时就直接 r e t u r n return return了,当然也没有各种阶乘逆元什么的

  • 下次学多项式求逆再来做一次这题 (FLAG)


code

#pragma GCC optimize("O3")
#pragma G++ optimize("O3")
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define MAXN 400005
#define G 3
#define mod 998244353
#define ll long long
#define reg register ll
#define fo(i,a,b) for (reg i=a;i<=b;++i)
#define fd(i,a,b) for (reg i=a;i>=b;--i)

using namespace std;

ll f[MAXN],g[MAXN];
ll a[MAXN],b[MAXN],rev[MAXN];
ll n,m;

inline ll read()
{
	ll x=0,f=1;char ch=getchar();
	while (ch<'0' || '9'<ch){if (ch=='-')f=-1;ch=getchar();}
	while ('0'<=ch && ch<='9')x=x*10+ch-'0',ch=getchar();
	return x*f;
}
inline ll work(ll x)
{
	ll y=1;
	while (y<x)y<<=1;
	return y<<1;
}
inline ll pow(ll x,ll y)
{
	ll z=1;
	while (y)
	{
		if (y&1)z=z*x%mod;
		x=x*x%mod,y>>=1;
	}
	return z;
}
inline void ntt(ll a[],ll len,ll inv)
{
	ll bit=0;
	while ((1<<bit)<len)++bit;
	fo(i,0,len-1)
	{
		rev[i]=(rev[i>>1]>>1)|((i&1)<<(bit-1));
		if (i<rev[i])swap(a[i],a[rev[i]]);
	}
	for (ll mid=1;mid<len;mid*=2)
	{
		ll tmp=pow(G,(mod-1)/(mid*2));
		if (inv==-1)tmp=pow(tmp,mod-2);
		for (ll i=0;i<len;i+=mid*2)
		{
			ll omega=1;
			for (ll j=0;j<mid;++j,omega=omega*tmp%mod)
			{
				ll x=a[i+j],y=omega*a[i+j+mid]%mod;
				a[i+j]=(x+y)%mod,a[i+j+mid]=(x-y+mod)%mod;
			}
		}
	}
}
inline void CDQ(ll l,ll r)
{
	if (l==r)return;
	ll mid=(l+r)>>1;
	CDQ(l,mid);
	ll len=work(r-l+1),invv=pow(len,mod-2);
	fo(i,0,len-1)a[i]=b[i]=0;
	fo(i,1,mid-l+1)a[i]=f[l+i-1];
	fo(i,1,r-l)b[i]=g[i];
	ntt(a,len,1),ntt(b,len,1);
	fo(i,0,len-1)a[i]=(a[i]*b[i]%mod);
	ntt(a,len,-1);
	fo(i,0,len-1)a[i]=(a[i]*invv)%mod;
	fo(i,mid+1,r)(f[i]+=a[i-l+1])%=mod;
	CDQ(mid+1,r);
}
int main()
{
	freopen("CDQNTT.in","r",stdin);
	n=read(),m=work(n);
	fo(i,1,n-1)g[i]=read();
	f[1]=1,CDQ(1,m/2);
	fo(i,1,n)printf("%lld ",f[i]);
	printf("\n");
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值