洛谷 P5224 - Candies(循环卷积)

洛谷题面传送门

一道题解长度大概不到 1k 的题,可还是决定写篇题解,因为自己没有做出来(

\(1004535809\) 好评(

首先这个 \(\equiv m\pmod{k}\) 有点把我们往单位根反演的方向思考的意思,不过注意到 \(k\) 不一定是 \(1004535808\) 的约数,因此在多数情况下 \(k\) 次单位根是不存在的,因此我们只能放弃这个想法。

然后我便想着如何裂组合数,发现裂了之后还要再裂,如此不停递归下去,进而获得了复杂度优秀的 \(\mathcal O(nk)\) 的做法。看了题解才发现自己是多么 sb……

注意到这个组合数很容易让我们与二项式产生联想,具体来说 \(\dbinom{n}{i}=[x^i](1+x)^n\),而比较巧的一点是我们求和的组合数中所有组合数的上部都是相同的,也就是说答案就是 \((1+x)^n\) 中所有次数 \(\bmod k=m\) 的项的系数之和。看到次数 \(\bmod k\) 又能让我们发生怎样的联想呢?……循环卷积!我们考虑求 \((1+x)^n\) 时,对次数做 \(\bmod k\) 意义下的循环卷积,这样最终答案就是求得的长度为 \(k\) 的多项式中,\(x^m\) 前的系数。注意到 NTT 是可以解决循环卷积问题的,因此 NTT+循环卷积即可。

又帮我复习了一个 9 个月前学的东西(bushi,因为这东西大概 3 个月后还要再次学到)

const int MAXP=32768;
const int pr=3;
const int ipr=(MOD+1)/3;
void add(int &x,int v){((x+=v)>=MOD)&&(x-=MOD);}
int qpow(int x,int e){
	int ret=1;
	for(;e;e>>=1,x=1ll*x*x%MOD) if(e&1) ret=1ll*ret*x%MOD;
	return ret;
}
ll n;int m,K,rev[MAXP+5];
void NTT(vector<int> &a,int len,int type){
	int lg=31-__builtin_clz(len);
	for(int i=0;i<len;i++) rev[i]=(rev[i>>1]>>1)|((i&1)<<lg-1);
	for(int i=0;i<len;i++) if(rev[i]<i) swap(a[i],a[rev[i]]);
	static int pw_W[MAXP+5];pw_W[0]=1;
	for(int i=2;i<=len;i<<=1){
		int W=qpow((type<0)?ipr:pr,(MOD-1)/i);
		for(int j=((i>>1)-2);j>=0;j-=2) pw_W[j+1]=1ll*(pw_W[j]=pw_W[j>>1])*W%MOD;
		for(int j=0;j<len;j+=i){
			for(int k=0;k<(i>>1);k++){
				int X=a[j+k],Y=1ll*a[(i>>1)+j+k]*pw_W[k]%MOD;
				a[j+k]=(X+Y>=MOD)?(X+Y-MOD):(X+Y);
				a[(i>>1)+j+k]=(X-Y<0)?(X-Y+MOD):(X-Y);
			}
		}
	}
	if(!~type){
		int ivn=qpow(len,MOD-2);
		for(int i=0;i<len;i++) a[i]=1ll*a[i]*ivn%MOD;
	}
}
vector<int> conv(vector<int> a,vector<int> b){
	int LEN=1;while(LEN<a.size()+b.size()) LEN<<=1;
//	printf("A: ");for(int i=0;i<a.size();i++) printf("%d%c",a[i]," \n"[i+1==a.size()]);
//	printf("B: ");for(int i=0;i<b.size();i++) printf("%d%c",b[i]," \n"[i+1==b.size()]);
	a.resize(LEN,0);b.resize(LEN,0);NTT(a,LEN,1);NTT(b,LEN,1);
	for(int i=0;i<LEN;i++) a[i]=1ll*a[i]*b[i]%MOD;NTT(a,LEN,-1);
//	printf("mul: ");for(int i=0;i<a.size();i++) printf("%d%c",a[i]," \n"[i+1==a.size()]);
	while(a.size()>K){
		int pos=(int)(a.size())-1;
		add(a[pos%K],a[pos]);a.ppb();
	} return a;
}
int main(){
	scanf("%lld%d%d",&n,&m,&K);
	vector<int> res(1,1),trs(2,1);
	for(;n;n>>=1,trs=conv(trs,trs)) if(n&1) res=conv(res,trs);
	if(m>=res.size()) printf("0\n");
	else printf("%d\n",res[m]);
	return 0;
}
上一篇:1103.分糖果II


下一篇:【AtCoder】C - Colorful Candies map+双指针