Codeforces 1096G Lucky Tickets

本文深入探讨了一种新颖的多项式快速幂算法,利用NTT进行优化,实现O(nlogn)复杂度。文章详细介绍了如何通过一次NTT变换,将多项式中的每个元素提升到k次幂,再通过逆NTT变换恢复,以此解决特定数学问题。这种方法避免了多次NTT变换带来的高时间成本。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意:0~9种有k种可用数字,求前n/2个数字和与后n/2个数字和相等的方案数。n&lt;=200000且n为偶数n&lt;=200000 且 n为偶数n<=200000n

这题继续刷新我对多项式(常数)的认识。。。。。。
不难发现这个题就是多项式快速幂。。。。。。
然后我就想尝试一下新打的多项式expexpexp的版,O(nlog⁡n)O(n \log n)O(nlogn)是不是很资瓷啊。
然后极限数据连多项式求逆都要跑5s5s5s更不要说什么exp了。
正解居然是NTT一次然后对NTT后的数组每一个数AAA变为AkA^kAk
然后再NTT回来这种操作。
其实早就想过这种操作了,可是碍于网上的代码都不这么写,就没怎么想。
今天来一发我自己的理解。
上面的那个操作是正确的,多项式系列算法其实有两个mod,一个是题目给的998244353,还有一个就是答案多项式是在modxnmod x^nmodxn下计算的,不然项数可能会爆炸。
如果题目的答案需要mod任意一个mod,那么都不能NTT一次最后再NTT回来,因为中途你得NTT回来mod(如果mod的不是998244353,那么得需要拆系数FFT或3模数来保证不会炸long double / LL , 如果需要mod x^n , 那么得需要NTT回来把 xnx^nxn以上的项全部清为0,不然NTT会变成循环卷积的,你的多项式求逆,求ln⁡\lnln,求exp⁡\expexp都是在这个意义下才成立的)。

所以如果是一个长度为10510^5105的多项式的10910^9109次幂 modx105mod x^{10^5}modx105,那么你NTT一次最后再NTT回来时你得开2∗10142*10^{14}21014的数组才能保证NTT不会变成循环卷积。。。。只能每次都NTT过去再NTT回来modxnmod x^nmodxn,或者多项式exp⁡\expexp

而这个题是长度为10的多项式的10510^5105次幂,当然可以(小常数)nlog⁡nn \log nnlogn的只NTT两次啦。

AC Code;

#include<bits/stdc++.h>
#define maxn (1<<21)+5
#define mod 998244353
using namespace std;

int r[maxn],inv[maxn]={1,1},w[maxn]={1},lg[maxn],wlen;
int Pow(int base,int k)
{ 	int ret = 1;
	for(;k;k>>=1,base=1ll*base*base%mod) if(k&1) ret=1ll * ret* base % mod;
	return ret;}
void Init(int n)
{	for(wlen=1;n>=(wlen<<1);wlen<<=1);
	for(int i=1,pw=Pow(3,(mod-1)/(2*wlen));i<=2*wlen;i++) w[i] = 1ll * w[i-1] * pw  % mod;
	for(int i=2;i<=2*wlen;i++) 
		lg[i] = lg[i>>1] + 1, inv[i] = 1ll * (mod - mod / i) * inv[mod % i] % mod;}
inline void NTT(int A[maxn],int n,int tp)
{	int lgn = lg[n];
	for(int i=1;i<n;i++) r[i]=(r[i>>1]>>1)|((i&1)<<(lgn-1));
	for(int i=1;i<n;i++) if(i<r[i]) swap(A[i],A[r[i]]);
	for(int L=2;L<=n;L<<=1)
		for(int st=0,l=L>>1,inc=wlen/l;st<n;st+=L)
			for(int k=st,x=0,tmp;k<st+l;k++,x+=inc)
				tmp=1ll*(tp==1?w[x]:w[2*wlen-x])*A[k+l]%mod,
				A[k+l]=(A[k]-tmp)%mod,A[k]=(A[k]+tmp)%mod;
	if(tp==-1) for(int i=0,inv=Pow(n,mod-2);i<n;i++) A[i]=1ll*A[i]*inv%mod;
}
void Inv(int *A,int *B,int n)
{	B[0]=Pow(A[0],mod-2),B[1]=0;
	static int tmp[maxn];
	for(int k=2;k<(n<<1);k<<=1)
	{	for(int i=0;i<(k<<1);i++) tmp[i]=i<k?A[i]:0,B[i]=i<k?B[i]:0;
		NTT(tmp,k<<1,1),NTT(B,k<<1,1);
		for(int i=0;i<(k<<1);i++) B[i]=1ll*B[i]*(2-1ll*B[i]*tmp[i]%mod)%mod;
		NTT(B,k<<1,-1);for(int i=min(n,k);i<(k<<1);i++) B[i]=0;}
}
void cLn(int *A,int *B,int n)
{	Inv(A,B,n);
	static int tmp[maxn];
	int lgn=lg[n-1]+2,len=1<<lgn;
	for(int i=0;i<len;i++) tmp[i]=i<n-1?1ll*A[i+1]*(i+1)%mod:0,B[i]=i<n?B[i]:0;
	NTT(tmp,len,1),NTT(B,len,1);
	for(int i=0;i<len;i++) tmp[i]=1ll*tmp[i]*B[i]%mod;
	NTT(tmp,len,-1);B[0]=0;
	for(int i=1;i<n;i++) B[i]=1ll*tmp[i-1]*inv[i]%mod;
	for(int i=n;i<len;i++) B[i] = 0;
}
void eXp(int *A,int *B,int n)
{	B[0]=1,B[1]=0;
	static int tmp[maxn];
	for(int k=2;k<(n<<1);k<<=1)
	{	cLn(B,tmp,k);
		for(int i=0;i<(k<<1);i++) tmp[i]=i<k?((i==0)-tmp[i]+A[i])%mod:0,B[i]=i<k?B[i]:0;
		NTT(tmp,k<<1,1),NTT(B,k<<1,1);
		for(int i=0;i<(k<<1);i++) B[i]=1ll*B[i]*tmp[i]%mod;
		NTT(B,k<<1,-1);for(int i=min(n,k);i<(k<<1);i++) B[i] = 0;
	}
}
void Pow(int *A,int *B,int n,int k)
{	int lgn = lg[n-1]+2 , len = 1 << lgn;
	for(int i=0;i<len;i++) B[i] = i<n?A[i]:0;
	NTT(B,len,1);
	for(int i=0;i<len;i++) B[i]=Pow(B[i],k);
	NTT(B,len,-1);
}
int a[maxn],b[maxn];
int main()
{
	int n,k,d[20]={};
	scanf("%d%d",&n,&k);
	for(int i=0;i<k;i++) scanf("%d",&d[i]);
	sort(d,d+k);
	for(int i=0;i<k;i++) a[d[i]-d[0]]++;
	int tp = d[k-1] - d[0];
	Init(n*10);
	Pow(a,b,n/2*tp+1,n/2);
	int ans = 0;
	for(int i=0;i<n*5;i++)
		ans = (ans + 1ll * b[i] * b[i] % mod) % mod;
	printf("%d\n",(ans+mod)%mod);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值