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

宝石装箱

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

思路:

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

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

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

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

设 U 种元素有 n 种不同的属性,而第 i 种属性称为 P i P_i Pi,拥有属性 P i P_i Pi的元素构成集合 S i S_i Si,那么
∣ ⋃ i = 1 n S i ∣ = ∑ i ∣ S i ∣ − ∑ i < j ∣ S i ∩ S j ∣ + ∑ i < j < k ∣ S i ∩ S j ∩ S k ∣ − ⋯ + ( − 1 ) m − 1 ∑ a i < a i + 1 ∣ ⋂ i = 1 m S a i ∣ + ⋯ + ( − 1 ) n − 1 ∣ S 1 ∩ ⋯ ∩ S n ∣ |\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=iSii<jSiSj+i<j<kSiSjSk+(1)m1ai<ai+1i=1mSai++(1)n1S1Sn

∣ ⋃ i = 1 n S i ∣ = ∑ m = 1 n ( − 1 ) m − 1 ∑ a i < a i + 1 ∣ ⋂ i = 1 m S a i ∣ |\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}| i=1nSi=m=1n(1)m1ai<ai+1i=1mSai

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

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

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

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

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

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

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

代码:

#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);
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值