宝石装箱
传送门
题意:n个物品装入n个盒子,每个盒子都要装一个物品。第i个物品不能装进第a i a_i a i 个盒子。求合法的装法数。
思路:
第一眼看到这题,心想这不就是传说中的错排题目吗?
回忆一下错排 题目的解法:
设D n D_n D n 为n n n 个物品错排的方案数目。有D 1 = 0 D_1=0 D 1 = 0 ,D 2 = 1 D_2=1 D 2 = 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} = D n − 2
(2)当k k k ,没排在第n n n 位时,此时等价于D n − 1 D_{n-1} D n − 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}) D n = ( n − 1 ) ( D n − 1 + D n − 2 )
可惜的是,这道题没这么简单(。
注意哦,对于每一个盒子,可能有多个物品不能放进这里。而对于每个物品,他可以放进除了该盒子以外的所有盒子。
这时候dalao们 就要想到容斥原理 的作法了。
容斥精讲博文
如何容斥呢?
先认识一下容斥原理的公式 吧:
设 U 种元素有 n 种不同的属性,而第 i 种属性称为P i P_i P i ,拥有属性P i P_i P i 的元素构成集合S i S_i S i ,那么
∣ ⋃ 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 = 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 ∣
即
∣ ⋃ 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 = 1 n S i ∣ = ∑ m = 1 n ( − 1 ) m − 1 ∑ a i < a i + 1 ∣ ⋂ i = 1 m S a i ∣
我们的目的是求得所有盒子都合法的方案个数 。
设S i ‾ \overline{S_i} S i 表示第i i i 个盒子合法的方案集合。答案用集合表示就是∣ ⋂ i = 1 n S i ‾ ∣ |\bigcap_{i=1}^n\overline {S_i}| ∣ ⋂ i = 1 n S i ∣ 。(”∣ | ∣ “表示元素个数还记得吗)
由补集,我们知道
∣ ⋂ 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 = 1 n S i ∣ = ∣ U ∣ − ∣ ⋃ i = 1 n S i ∣
易知∣ U ∣ = n ! |U|=n! ∣ U ∣ = n ! ,因此,我们可以通过计算∣ ⋃ i = 1 n S i ∣ |\bigcup_{i=1}^n{S_i}| ∣ ⋃ i = 1 n S i ∣ 得到答案。
而∣ ⋃ i = 1 n S i ∣ |\bigcup_{i=1}^n{S_i}| ∣ ⋃ i = 1 n S i ∣ 可以由容斥原理转化为
∑ 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 = 1 n ( − 1 ) m − 1 ∑ a i < a i + 1 ∣ ⋂ i = 1 m S a i ∣ 。
因此,只需要求出来∑ a i < a i + 1 ∣ ⋂ i = 1 m S a i ∣ \sum_{a_i<a_{i+1}}|\bigcap_{i=1}^mS_{a_i}| ∑ a i < a i + 1 ∣ ⋂ i = 1 m S a i ∣ 即可。
观察一遍容斥原理的定义,把这 条式子转化为文字描述:
在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 = 1 n ( − 1 ) m − 1 ∑ a i < a i + 1 ∣ ⋂ i = 1 m S a i ∣ 这整个式子会把重复的方案减掉。因此,这句描述可以理解为,每“至少m个盒子不合法的并集”的总和。
我们可以通过求每“m个盒子不合法的并集”的总和(不考虑其他盒子的是非)来求 至少 :在求到不考虑其他盒子的值之后,只需要乘上( n − m ) ! (n-m)! ( n − m ) ! 即可。
那如何求刚好 呢?
通过动态规划 。设D P ( i , j ) DP(i,j) D P ( i , j ) 为考虑到第i i i 个盒子的时候,已经发现j j j 个盒子非法的方案数目,D P ( n , 0 ) = 1 DP(n,0)=1 D P ( n , 0 ) = 1 ,D P ( 0 , 0 ) = 1 DP(0,0)=1 D P ( 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] D P ( i , j ) = D P ( i − 1 , j ) + D P ( i − 1 , j − 1 ) ∗ b [ i ] 。
此处的b [ i ] b[i] b [ i ] 为当前盒子要非法时,可以选择的物品数目。注意,因为我们的D P DP D P 考虑的是已经发现非法 的方案数目,因此这个b [ i ] b[i] b [ i ] 的选取并无后效性。(对于每个物品,他只有一个盒子不能放,这b [ i ] b[i] b [ i ] 个物品不可能出现在状态D P ( i − 1 , j − 1 ) DP(i-1,j-1) D P ( 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 = 1 n ( − 1 ) m − 1 ∑ a i < a i + 1 ∣ ⋂ i = 1 m S a i ∣
即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 = 1 n ( − 1 ) m D P ( 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 = 0 n ( − 1 ) m D P ( 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) ;
}