[ACM]【容斥原理/揹包DP】牛客练习赛64 宝石装箱

宝石装箱

传送门
题意:n个物品装入n个盒子,每个盒子都要装一个物品。第i个物品不能装进第aia_i个盒子。求合法的装法数。
图

思路:

第一眼看到这题,心想这不就是传说中的错排题目吗?
回忆一下错排题目的解法:

DnD_nnn个物品错排的方案数目。有D1=0D_1=0D2=1D_2=1
n3n\geq 3时,设nn排在第kk位,其中knk\not =n,也就是1kn11\leq k\leq n-1。那现在考虑kk的情况:
(1)当kk放在第nn位时,错排数=Dn2=D_{n-2}
(2)当kk,没排在第nn位时,此时等价于Dn1D_{n-1}
kk有从11n1n-1n1n-1种取法。
所以答案为Dn=(n1)(Dn1+Dn2)D_n=(n-1)(D_{n-1}+D_{n-2})

可惜的是,这道题没这么简单(。
注意哦,对于每一个盒子,可能有多个物品不能放进这里。而对于每个物品,他可以放进除了该盒子以外的所有盒子。

这时候dalao们就要想到容斥原理的作法了。
容斥精讲博文
如何容斥呢?
先认识一下容斥原理的公式吧:

设 U 种元素有 n 种不同的属性,而第 i 种属性称为PiP_i,拥有属性PiP_i的元素构成集合SiS_i,那么
i=1nSi=iSii<jSiSj+i<j<kSiSjSk+(1)m1ai<ai+1i=1mSai++(1)n1S1Sn|\bigcup_{i=1}^nS_i|=\sum_i|S_i|-\sum_{i<j}|S_i\cap S_j|+\sum_{i<j<k}|S_i\cap S_j\cap S_k|-\cdots +(-1)^{m-1}\sum_{a_i<a_{i+1}}|\bigcap_{i=1}^mS_{a_i}|+\cdots+(-1)^{n-1}|S_1\cap\cdots\cap S_n|

i=1nSi=m=1n(1)m1ai<ai+1i=1mSai|\bigcup_{i=1}^n S_i|=\sum_{m=1}^n(-1)^{m-1}\sum_{a_i<a_{i+1}}|\bigcap_{i=1}^mS_{a_i}|

我们的目的是求得所有盒子都合法的方案个数
Si\overline{S_i}表示第ii个盒子合法的方案集合。答案用集合表示就是i=1nSi|\bigcap_{i=1}^n\overline {S_i}|。(”|“表示元素个数还记得吗)
由补集,我们知道
i=1nSi=Ui=1nSi|\bigcap_{i=1}^n\overline {S_i}|=|U|-|\bigcup_{i=1}^n{S_i}|
易知U=n!|U|=n!,因此,我们可以通过计算i=1nSi|\bigcup_{i=1}^n{S_i}|得到答案。
i=1nSi|\bigcup_{i=1}^n{S_i}|可以由容斥原理转化为
m=1n(1)m1ai<ai+1i=1mSai\sum_{m=1}^n(-1)^{m-1}\sum_{a_i<a_{i+1}}|\bigcap_{i=1}^mS_{a_i}|

因此,只需要求出来ai<ai+1i=1mSai\sum_{a_i<a_{i+1}}|\bigcap_{i=1}^mS_{a_i}|即可。
观察一遍容斥原理的定义,把条式子转化为文字描述:
在n个盒子中,每“m个盒子不合法的并集”的总和。

仔细理解这句描述,因为在这句描述中,只考虑了m个盒子的状态,此时其他(n-m)个盒子的状态如何,是无所谓的。他们可以是合法的,亦可以是非法的。因为容斥过了,m=1n(1)m1ai<ai+1i=1mSai\sum_{m=1}^n(-1)^{m-1}\sum_{a_i<a_{i+1}}|\bigcap_{i=1}^mS_{a_i}|这整个式子会把重复的方案减掉。因此,这句描述可以理解为,每“至少m个盒子不合法的并集”的总和。

我们可以通过求每“m个盒子不合法的并集”的总和(不考虑其他盒子的是非)来求至少:在求到不考虑其他盒子的值之后,只需要乘上(nm)!(n-m)!即可。

那如何求刚好呢?
通过动态规划。设DP(i,j)DP(i,j)为考虑到第ii个盒子的时候,已经发现jj个盒子非法的方案数目,DP(n,0)=1DP(n,0)=1DP(0,0)=1DP(0,0)=1(因为我们不考虑其他的盒子的是非)。我们得到状态转移:
DP(i,j)=DP(i1,j)+DP(i1,j1)b[i]DP(i,j)=DP(i-1,j)+DP(i-1,j-1)*b[i]
此处的b[i]b[i]为当前盒子要非法时,可以选择的物品数目。注意,因为我们的DPDP考虑的是已经发现非法的方案数目,因此这个b[i]b[i]的选取并无后效性。(对于每个物品,他只有一个盒子不能放,这b[i]b[i]个物品不可能出现在状态DP(i1,j1)DP(i-1,j-1)j1j-1个盒子之中)

答案为Um=1n(1)m1ai<ai+1i=1mSai|U|-\sum_{m=1}^n(-1)^{m-1}\sum_{a_i<a_{i+1}}|\bigcap_{i=1}^mS_{a_i}|
n!+m=1n(1)mDP(n,m)×(nm)!n!+\sum_{m=1}^n(-1)^mDP(n,m)\times (n-m)!
m=0n(1)mDP(n,m)×(nm)!\sum_{m=0}^n(-1)^mDP(n,m)\times (n-m)!

在实现的过程中,用了空间优化。

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=8004;
const int mod=998244353;
ll dp[maxn],b[maxn],fac[maxn];
int main(){
	int n;
	fac[0]=1;
	for(int i=1;i<=maxn;i++) fac[i]=fac[i-1]*i%mod;
	scanf("%d",&n);
	int tmp;
	for(int i=1;i<=n;i++){
		scanf("%d",&tmp);
		b[tmp]++;
	}
	dp[0]=1;
	for(int i=1;i<=n;i++){
		for(int j=i;j>=1;j--){
			dp[j]=(dp[j]+dp[j-1]*b[i])%mod;
		}
	}
	ll ans=0;
	for(int i=0;i<=n;i++){
		if(i&1) ans-=dp[i]*fac[n-i];
		else ans+=dp[i]*fac[n-i];
		ans=(ans+mod)%mod;
	}
	printf("%lld\n",ans);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章