宝石装箱
传送门
题意: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 n≥3时,设 n n n排在第 k k k位,其中 k ≠ n k\not =n k=n,也就是 1 ≤ k ≤ n − 1 1\leq k\leq n-1 1≤k≤n−1。那现在考虑 k k k的情况:
(1)当 k k k放在第 n n n位时,错排数 = D n − 2 =D_{n-2} =Dn−2
(2)当 k k k,没排在第 n n n位时,此时等价于 D n − 1 D_{n-1} Dn−1
而 k k k有从 1 1 1到 n − 1 n-1 n−1共 n − 1 n-1 n−1种取法。
所以答案为 D n = ( n − 1 ) ( D n − 1 + D n − 2 ) D_n=(n-1)(D_{n-1}+D_{n-2}) Dn=(n−1)(Dn−1+Dn−2)
可惜的是,这道题没这么简单(。
注意哦,对于每一个盒子,可能有多个物品不能放进这里。而对于每个物品,他可以放进除了该盒子以外的所有盒子。
这时候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∣=∑i∣Si∣−∑i<j∣Si∩Sj∣+∑i<j<k∣Si∩Sj∩Sk∣−⋯+(−1)m−1∑ai<ai+1∣⋂i=1mSai∣+⋯+(−1)n−1∣S1∩⋯∩Sn∣
即
∣ ⋃ 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)m−1∑ai<ai+1∣⋂i=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∣=∣U∣−∣⋃i=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)m−1∑ai<ai+1∣⋂i=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+1∣⋂i=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)m−1∑ai<ai+1∣⋂i=1mSai∣这整个式子会把重复的方案减掉。因此,这句描述可以理解为,每“至少m个盒子不合法的并集”的总和。
我们可以通过求每“m个盒子不合法的并集”的总和(不考虑其他盒子的是非)来求至少:在求到不考虑其他盒子的值之后,只需要乘上 ( n − m ) ! (n-m)! (n−m)!即可。
那如何求刚好呢?
通过动态规划。设
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(i−1,j)+DP(i−1,j−1)∗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(i−1,j−1)那
j
−
1
j-1
j−1个盒子之中)
答案为
∣
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}|
∣U∣−∑m=1n(−1)m−1∑ai<ai+1∣⋂i=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)×(n−m)!
即
∑
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)×(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);
}