P5748 集合划分计数 题解

传送门

考虑先对所有的 \(n\) 求出答案,每次询问 \(O(1)\) 输出。

\(f_n\) 表示 \(n\) 的答案,根据题意易得:

\[f_n=\sum_{i=1}^n {n-1\choose i-1}f_{n-i} \]

组合数展开并分离系数得:

\[f_n=\sum_{i=1}^n \frac{(n-1)!}{(i-1)!}\times \frac{f_{n-i}}{(n-i)!} \]

\[\frac{f_n}{(n-1)!}=\sum_{i=1}^n \frac{f_{n-i}}{(n-i)!}\times \frac{1}{(i-1)!} \]

\(g_n=\frac{f_n}{(n-1)!}\),得:

\[g_n=\sum_{i=1}^n \frac{g_{n-i}}{n-i}\times \frac{1}{(i-1)!} \]

这是一个卷积的形式,分治 NTT 即可。时间复杂度 \(O(n\log^2n)\)

参考代码:

#include<bits/stdc++.h>
#define int long long
#define mxn 524288
#define md 998244353
#define rep(i,a,b) for(int i=a;i<=b;++i)
#define rept(i,a,b) for(int i=a;i<b;++i)
#define drep(i,a,b) for(int i=a;i>=b;--i)
using namespace std;
int power(int x,int y){
	int ans=1;
	for(;y;y>>=1){
		if(y&1)ans=ans*x%md;
		x=x*x%md;
	}
	return ans;
}
int T,n,dp[mxn],f[mxn],g[mxn],rev[mxn],fac[mxn],ifac[mxn];
void ntt(int *a,int n,int flag){
	rept(i,0,n)if(i<rev[i])swap(a[i],a[rev[i]]);
	for(int h=1;h<n;h<<=1){
		int x,y,s=power(3,499122176/h);
		for(int j=0;j<n;j+=h<<1){
			int w=1;
			for(int k=j;k<j+h;++k){
				x=a[k],y=w*a[k+h]%md;
				a[k]=(x+y)%md;
				a[k+h]=(x-y+md)%md;
				w=w*s%md;
			}
		}
	}
	if(flag==-1){
		int p=power(n,md-2);
		reverse(a+1,a+n);
		rept(i,0,n)a[i]=a[i]*p%md;
	}
}
void solve(int l,int s,int k){
	if(s==1)return;
	solve(l,s>>1,k-1);
	rept(i,0,s>>1){
		f[i]=l+i?dp[l+i]*ifac[l+i]%md*fac[l+i-1]%md:1;
		g[i]=ifac[i];
	}
	rept(i,s>>1,s)f[i]=0,g[i]=ifac[i];
	rept(i,0,s)rev[i]=(rev[i>>1]>>1)|((i&1)<<(k-1));
	ntt(f,s,1);ntt(g,s,1);
	rept(i,0,s)f[i]=f[i]*g[i]%md;
	ntt(f,s,-1);
	rept(i,s>>1,s)dp[l+i]=(dp[l+i]+f[i-1])%md;
	solve(l+(s>>1),s>>1,k-1);
}
signed main(){
	fac[0]=1;
	rep(i,1,131072)fac[i]=fac[i-1]*i%md;
	ifac[131072]=power(fac[131072],md-2);
	drep(i,131072,1)ifac[i-1]=ifac[i]*i%md;
	dp[0]=1;
	solve(0,131072,17);
	scanf("%lld",&T);
	while(T--){
		scanf("%lld",&n);
		printf("%lld\n",dp[n]*fac[n-1]%md);
	}
	return 0;
}
posted @ 2023-11-05 12:52  zifanwang  阅读(2)  评论(0编辑  收藏  举报  来源