寶石裝箱
傳送門
題意: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) ;
}