CF1096G Lucky Tickets
前言
挺简单的一道题,NTT 入门。
对于之后理解生成函数应该有很大帮助。
解法
先想想这个计数怎么计。
设选 \(\frac{n}{2}\) 个数和为 \(s\) 的方案数为 \(f_s\)。那么最后我们求得答案就是:
\[\sum\limits_{s=0}^\frac{9n}{2} {f_s}^2 \]这个读者自证不难
那么问题来了,怎样快速求出 \(f_s\)?我们可以想到构造多项式来解决。
比如对于样例一:
4 2
1 8
我们可以构造一个多项式 \(g(x)=x+x^8\)。也就是,把每个可以选用的数字,作为次数的那一项前的系数设为 \(1\),其余为 \(0\)。
这样有什么好处呢?
再来看这个样例,我们求的是选择 \(2\) 个数和为 \(s\) 的方案数。那么我们就将 \(g(x)\) 平方,得到 \(x^2+2x^9+x^{16}\),这样就代表 \(f_2=1,f_9=2,f_{16}=1\)。
这其实是用到了多项式乘法次数相加,系数相乘的性质。这个相加也就是我们的数字相加。因此这样计算是可以的。
所以最后归纳一下,我们要做以下步骤:
- 构造 \(g(x)\)。
- 计算 \(h(x)=g(x)^{\frac{n}{2}}\)。
- 根据公式求出 \(ans\)。
代码
不会有人想用 FFT 吧,不会吧,不会吧。
小丑竟是我自己。
//Don't act like a loser.
//This code is written by huayucaiji
//You can only use the code for studying or finding mistakes
//Or,you'll be punished by Sakyamuni!!!
#include<bits/stdc++.h>
#define int long long
using namespace std;
int read() {
char ch=getchar();
int f=1,x=0;
while(ch<'0'||ch>'9') {
if(ch=='-')
f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9') {
x=x*10+ch-'0';
ch=getchar();
}
return f*x;
}
const int MAXN=3e6+10;
const int G=3;
const int MOD=998244353;
int n,k;
int f[MAXN],r[MAXN];
int qpow(int x,int y) {
int ret=1;
while(y) {
if(y&1) {
ret=x*ret%MOD;
}
y>>=1;
x=x*x%MOD;
}
return ret;
}
void NTT(int A[],int d,int inv) {
for(int i=0;i<d;i++) {
if(i<r[i]) {
swap(A[i],A[r[i]]);
}
}
for(int len=1;len<d;len<<=1) {
int gn=qpow(G,(MOD-1)/(len*2));
for(int i=0;i<d;i+=len<<1) {
int g=1;
for(int k=0;k<len;k++,g=g*gn%MOD) {
int x=A[i+k];
int y=g*A[i+k+len]%MOD;
A[i+k]=(x+y)%MOD;
A[i+k+len]=(x-y+MOD)%MOD;
}
}
}
if(inv==-1) {
reverse(A+1,A+d);
int g=qpow(d,MOD-2);
for(int i=0;i<d;i++) {
A[i]=A[i]*g%MOD;
}
}
}
signed main() {
n=read();k=read();
while(k--) {
f[read()]=1;
}
int d=1,bit=0;
while(d<=n/2*9) {
d<<=1;
bit++;
}
for(int i=0;i<d;i++) {
r[i]=(r[i>>1]>>1)|((i&1)<<(bit-1));
}
NTT(f,d,1);
for(int i=0;i<d;i++) {
f[i]=qpow(f[i],n/2);
}
NTT(f,d,-1);
int ans=0;
for(int i=0;i<d;i++) {
ans=(ans+f[i]*f[i]%MOD)%MOD;
}
cout<<ans<<endl;
return 0;
}