NFLSOJ #12473 -「NOIP2021模拟赛1007华二」kurumi(容斥+背包)

题面传送门

笑死,我已经落魄到连容斥都想不到的地步了吗/ll

orz ycx 爆切容斥 %%%

首先考虑字典树节点的实际意义:\(m\) 个字符串的本质不同前缀个数,也就是全部 \(m\) 个串的前缀组成的集合的并集大小。注意到这个并的大小很难求,因此我们考虑容斥,即,枚举一个集合 \(S\),计算 \(S\) 中所有字符串公共前缀长度的期望值,答案乘上 \((-1)^{|S|-1}\),这样可以将并的大小转化为交的大小

考虑如何求出交的大小的期望值,首先有一个非常经典的套路,我们考虑对所有长度 \(l\) 计算出 \(S\) 中字符串存在长度为 \(l\) 的公共前缀的概率,把它们加起来即可得到答案,因为这样对于一种公共前缀长度为 \(len\) 的情况,其贡献 \(len\) 会在 \(1,2,3,\cdots,len\) 处各被计算依次计算一次。那么 \(S\) 中字符串存在长度为 \(l\) 的公共前缀的概率又该怎么算呢?考虑枚举这个公共前缀中 \(0\sim 9\) 各出现了多少次然后计算出这种情况对应的方案数,最后除以总方案数就是概率,即

\[\sum\limits_{t_0+t_1+\cdots+t_9=l}f(t_0,t_1,t_2,\cdots,t_9,S)·\prod\limits_{x\notin S}num_x \]

其中 \(num_i\) 表示第 \(i\) 个字符串经过重排后可以得到的字符串的数量,\(f(t_0,t_1,\cdots,t_9,S)\) 表示有多少种安排 \(S\) 中集合中字符串的方法,满足 \(S\) 中所有字符串都有长度为 \(l\) 的由 \(t_0\) 个 \(0\),\(t_1\) 个 \(1\),……,\(t_9\) 个 \(9\) 组成的公共前缀的方案数。\(num\) 显然可以通过多重集组合数预处理出来,考虑如何化简 \(f\),首先长度为 \(l\) 的公共前缀的方案数可以多重集组合数算出,即 \(\dfrac{l!}{\prod\limits_{i=0}^9t_i!}\),而对于剩余部分,还是一个多重集组合数的模型,即 \(\prod\limits_{x\in S}\dfrac{(m-l)!}{\prod\limits_{i=0}^9(cnt_{x,i}-t_i)!}\),其中 \(cnt_{x,i}\) 表示第 \(x\) 个字符串中有多少个 \(i\)。故

\[f(t_0,t_1,t_2,\cdots,t_9,S)=\dfrac{l!}{\prod\limits_{i=0}^9t_i!}·\prod\limits_{x\in S}\dfrac{(m-l)!}{\prod\limits_{i=0}^9(cnt_{x,i}-t_i)!} \]

注意到这个 \(\sum\limits_{i=0}^9t_i=l\) 的限制可以启发我们往背包的方向思考。在 \(f\) 计算的式子中,我们发现分母上的东西都与 \(t_i\) 有关,而分子上的东西都与 \(l\) 有关,因此考虑将分母上的东西放到背包的状态中,分子上的东西在枚举 \(l\) 的过程中算贡献,即,设 \(dp_{i,j}\) 表示目前确定了 \(t_0,t_1,\cdots,t_{i-1}\),\(\sum\limits_{k=0}^{i-1}t_k=j-1\) 的所有 \(t\) 序列的 \(\dfrac{1}{\prod\limits_{i=0}^9t_i!}·\prod\limits_{x\in S}\dfrac{1}{\prod\limits_{i=0}^9(cnt_{x,i}-t_i)!}\) 之和,背包一下即可。

时间复杂度 \(2^nm^2·(n+10)\)

const int MAXM=100;
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;
}
int n,m,cnt[12][12],fac[MAXM+5],ifac[MAXM+5],num[MAXM+5];
void init_fac(int n){
	for(int i=(fac[0]=ifac[0]=ifac[1]=1)+1;i<=MAXM;i++) ifac[i]=1ll*ifac[MOD%i]*(MOD-MOD/i)%MOD;
	for(int i=1;i<=MAXM;i++) fac[i]=1ll*fac[i-1]*i%MOD,ifac[i]=1ll*ifac[i-1]*ifac[i]%MOD;
}
string s[12];
int dp[12][MAXM+5];
int solve(int msk){
	memset(dp,0,sizeof(dp));dp[0][0]=1;
	for(int i=0;i<10;i++){
		int mn=INF;
		for(int j=1;j<=n;j++) if(msk>>(j-1)&1) chkmin(mn,cnt[j][i]);
		static int mul[MAXM+5];memset(mul,0,sizeof(mul));
		for(int j=0;j<=mn;j++){
			mul[j]=ifac[j];
			for(int k=1;k<=n;k++) if(msk>>(k-1)&1) mul[j]=1ll*mul[j]*ifac[cnt[k][i]-j]%MOD;
		}
		for(int j=0;j<=m;j++) if(dp[i][j]){
			for(int k=0;k<=mn;k++) dp[i+1][j+k]=(dp[i+1][j+k]+1ll*dp[i][j]*mul[k])%MOD;
		}
	} int res=0;
	for(int j=0;j<=m;j++){
		int mul=qpow(fac[m-j],__builtin_popcount(msk));
		mul=1ll*mul*fac[j]%MOD;mul=1ll*mul*dp[10][j]%MOD;
		res=(res+mul)%MOD;
	} for(int j=1;j<=n;j++) if(~msk>>(j-1)&1) res=1ll*res*num[j]%MOD;
//	printf("%d %d\n",msk,res);
	return res;
}
int main(){
	freopen("kurumi.in","r",stdin);
	freopen("kurumi.out","w",stdout);
	scanf("%d%d",&n,&m);init_fac(MAXM);
	for(int i=1;i<=n;i++) cin>>s[i];
	for(int i=1;i<=n;i++) for(int j=0;j<s[i].size();j++)
		cnt[i][s[i][j]-'0']++;
	for(int i=1;i<=n;i++){
		num[i]=fac[m];
		for(int j=0;j<10;j++) num[i]=1ll*num[i]*ifac[cnt[i][j]]%MOD;
//		printf("%d\n",num[i]);
	}
	int res=0;
	for(int i=1;i<(1<<n);i++){
		if(__builtin_popcount(i)&1) res=(res+solve(i))%MOD;
		else res=(res-solve(i)+MOD)%MOD;
	}
	for(int i=1;i<=n;i++) res=1ll*res*qpow(num[i],MOD-2)%MOD;
	printf("%d\n",res);
	return 0;
}
上一篇:[ARC127 E] Pass to Next —— 组合意义+DP容斥+环上DP


下一篇:题解[Zeta的数论题[加强版]]