<更新提示>
<第一次更新>
<正文>
容斥原理
基礎概念
我們假設有全集\(S\),以及\(n\)個集合\(A_1,A_2,...,A_n\),每個集合\(A_i\)中的元素具有性質\(P_i\),現在我們要求不具有任何性質的集合大小,也就是元素個數,則具有如下的計算式:
當然,我們還具有另一種形式:
這兩種形式可以簡單地互相轉換得到,本質相同。
如何理解其組合意義,我們可以這樣解釋:
形式\(1\):不具備任何性質的元素個數 \(=\) 就是元素總個數 \(-\) 至少具備一個性質的元素個數之和 \(+\) 至少具備兩個性質的元素個數知和 \(-\) 至少具備三個性質的元素個數之和 \(...\)
形式\(2\):所有集合的並集大小 \(=\) 所有集合的大小之和 \(-\) 每兩個集合之間的交集大小 \(+\) 每三個集合之間的交集大小 \(...\)
毒瘤選舉
Description
毒瘤選舉大會開始了。選舉大會的舉辦者Magolor不希望不毒瘤的人來當選,因為不毒瘤的人當選毒瘤領導人會讓這次選舉流芳百世、萬人傳頌,這是每個毒瘤都不想看到的。
因此,Magolor想要你(不管你是不是毒瘤)來幫他搞清楚有多大的概率這件事不會發生。毒瘤選舉大會有\(n\)位選民和\(k\)位候選人。由於選民也是毒瘤,所以選民會隨機投票(但幸好不會棄權或投多票)。一位候選人是毒瘤的當且僅當至少有一名選民給他投票。
現在,Magolor想知道在所有的投票方案中,有多少種方案滿足: 所有候選人都是毒瘤的。因為方案數特別多,Magolor只需要你輸出答案\(\bmod\ 998244363\)的余數即可。
Input Format
第一行輸入\(T\)表示有組\(T\)數據。
接下來行\(T\)每行兩個整數: \(n,k\)。
Output Format
輸出文件\(T\)行每行一個整數表示答案。
Sample Input
1
4 3
Sample Output
36
解析
我們可以把每一種投票方案看做一種元素,具備一個性質的元素就是有一個候選人沒選的方案。那么我們就可以套用容斥原理的第一個模型:沒有任何人落選的方案數就是全部方案數 \(-\) 至少有一個人落選的方案數之和 \(+\) 至少有兩個人落選的方案數之和 \(...\)
那么我們的問題就是快速計算至少有\(m\)個人落選的方案數之和,顯然,方案數即為:
那么根據容斥原理,答案即為:
注意,模數是\(998244363=3\times 19\times 97\times 180547\),需要先根據每一個模數算一個答案,然后用中國剩余定理合並答案。組合數需要用\(Lucas\)定理計算,時間復雜度\(O(k\times(\log_2 n+\log_{mod}k))\)。
從另一個角度考慮,\(ans=k!\times S(n,k)\),所以對於\(k\geq 180547\),答案為\(0\),直接輸出即可。
\(Code:\)
#include <bits/stdc++.h>
using namespace std;
const int N = 190000 , p[] = {0,3,19,97,180547};
int inv[5][N],fac[5][N],ans[5],n,m;
inline int add(int a,int b,int Mod) { return a + b >= Mod ? a + b - Mod : a + b; }
inline int mul(int a,int b,int Mod) { return 1LL * a * b % Mod; };
inline int sub(int a,int b,int Mod) { return a - b < 0 ? a - b + Mod : a - b; }
inline void Add(int &a,int b,int Mod) { a = add( a , b , Mod ); }
inline void Mul(int &a,int b,int Mod) { a = mul( a , b , Mod ); }
inline void Sub(int &a,int b,int Mod) { a = sub( a , b , Mod ); }
inline int quickpow(int a,int b,int Mod)
{
int res = 1;
for ( ; b ; Mul(a,a,Mod) , b>>=1 )
if ( 1 & b ) Mul(res,a,Mod);
return res;
}
inline void init(void)
{
for (int k=1;k<=4;k++)
{
fac[k][0] = inv[k][0] = 1;
for (int i=1;i<p[k];i++)
fac[k][i] = mul( fac[k][i-1] , i , p[k] );
inv[k][p[k]-1] = quickpow( fac[k][p[k]-1] , p[k]-2 , p[k] );
for (int i=p[k]-2;i>=1;i--)
inv[k][i] = mul( inv[k][i+1] , i+1 , p[k] );
}
}
inline int C(int n,int m,int k)
{
if ( n < m || n < 0 || m < 0 ) return 0;
int res = fac[k][n];
return mul( res , mul( inv[k][m] , inv[k][n-m] , p[k] ) , p[k] );
}
inline int Lucas(int n,int m,int k)
{
if ( m == 0 ) return 1;
int res = Lucas( n/p[k] , m/p[k] , k );
return mul( res , C( n%p[k] , m%p[k] , k ) , p[k] );
}
inline int Exeuclid(int a,int b,int &x,int &y)
{
if ( b == 0 ) return x = 1 , y = 0 , a;
int t = Exeuclid( b , a%b , x , y );
int _x = x , _y = y;
x = _y , y = _x - a / b * _y;
return t;
}
inline int ExCRT(void)
{
int M = p[1] , res = ans[1] % M , t , k , x , y;
for (int i=2;i<=4;i++)
{
k = ( (ans[i]-res) % p[i] + p[i] ) % p[i];
t = Exeuclid( M , p[i] , x , y );
x = x * (k/t) % (p[i]/t);
res = res + x * M;
M = M * p[i] / t;
res = ( res % M + M ) % M;
}
return res;
}
inline int solve(void)
{
for (int k=1;k<=4;k++)
{
ans[k] = quickpow( m , n , p[k] );
for (int i=1;i<=m;i++)
{
int val = mul( Lucas(m,i,k) , quickpow(m-i,n,p[k]) , p[k] );
if ( i & 1 ) Sub( ans[k] , val , p[k] );
else Add( ans[k] , val , p[k] );
}
}
}
int main(void)
{
init();
int T; long long N,M;
scanf("%d",&T);
while ( T --> 0 )
{
scanf("%lld%lld",&N,&M);
if ( M >= 180547 ) puts("0");
else n = N , m = M , solve(),
printf("%d\n",ExCRT());
}
return 0;
}
小w的喜糖
Description
廢話不多說,反正小w要發喜糖啦!!
小w一共買了n塊喜糖,發給了n個人,每個喜糖有一個種類。這時,小w突發奇想,如果這n個人相互交換手中的糖,那會有多少種方案使得每個人手中的糖的種類都與原來不同。
兩個方案不同當且僅當,存在一個人,他手中的糖的種類在兩個方案中不一樣。
Input Format
第一行,一個整數n
接下來n行,每行一個整數,第i個整數Ai表示開始時第i個人手中的糖的種類
對於所有數據,1≤Ai≤k,k<=N,N<=2000
Output Format
一行,一個整數Ans,表示方案數模1000000009
Sample Input
6
1
1
2
2
3
3
Sample Output
10
解析
首先我們認為同種糖的每一個也都是不同的,方便計數。
我們把一個方案看做一個元素,有一個人拿着原來和自己種類相同的糖看做滿足一個性質,然后套用容斥原理\(...\)
那么我們就要計算所有人中至少有\(i\)個人拿着和自己同種糖的方案數,可以考慮\(dp\)計數。
設\(f[i][j]\)代表前\(i\)種糖,有\(j\)個人拿着自己同種糖的方案數,可以直接轉移:
組合意義:前\(i-1\)種糖中已經有\(j-k\)個人拿着自己同種的糖了,現在第\(i\)種糖這樣的人要有\(k\)個,方案就是\(cnt[i]\)個人中選\(k\)個人,第一個人有\(cnt[i]\)種選擇選到同種糖,第二個人有\(cnt[i]-1\)種可能選到同種糖\(...\)
設\(m\)為顏色總數,然后容斥:
由於我們一開始把同種糖的每一個都看作本質不同的,所以最后要除掉每一種顏色出現次數的階乘。
\(Code:\)
#include <bits/stdc++.h>
using namespace std;
const int N = 2020 , Mod = 1e9+9;
int n,m,a[N],cnt[N],f[N][N],fac[N],inv[N],ans;
inline int add(int a,int b) { return a + b >= Mod ? a + b - Mod : a + b; }
inline int mul(int a,int b) { return 1LL * a * b % Mod; };
inline int sub(int a,int b) { return a - b < 0 ? a - b + Mod : a - b; }
inline void Add(int &a,int b) { a = add( a , b ); }
inline void Mul(int &a,int b) { a = mul( a , b ); }
inline void Sub(int &a,int b) { a = sub( a , b ); }
inline int quickpow(int a,int b) { int res = 1; for (;b;Mul(a,a),b>>=1) if ( 1 & b ) Mul(res,a); return res; }
inline void input(void)
{
scanf("%d",&n);
for (int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
cnt[a[i]]++;
}
}
inline void init(void)
{
fac[0] = inv[0] = 1;
for (int i=1;i<=n;i++)
fac[i] = mul( fac[i-1] , i );
inv[n] = quickpow( fac[n] , Mod-2 );
for (int i=n-1;i>=1;i--)
inv[i] = mul( inv[i+1] , i+1 );
sort( cnt+1 , cnt+n+1 );
reverse( cnt+1 , cnt+n+1 );
for (int i=1;i<=n+1;i++)
if ( cnt[i] == 0 ) { m = i-1 ; break; }
}
inline int C(int n,int m) { return mul( fac[n] , mul( inv[m] , inv[n-m] ) ); }
inline int A(int n,int m) { return mul( fac[n] , inv[n-m] ); }
inline void DynamicProgram(void)
{
f[0][0] = 1;
for (int i=1,lim=cnt[1];i<=m;lim+=cnt[++i])
for (int j=0;j<=lim;j++)
for (int k=0;k<=min(cnt[i],j);k++)
Add( f[i][j] , mul( f[i-1][j-k] , mul( C(cnt[i],k) , A(cnt[i],k) ) ) );
}
inline void solve(void)
{
for (int i=0;i<=n;i++)
if ( i & 1 ) Sub( ans , mul( f[m][i] , fac[n-i] ) );
else Add( ans , mul( f[m][i] , fac[n-i] ) );
for (int i=1;i<=m;i++)
Mul( ans , inv[cnt[i]] );
}
int main(void)
{
input();
init();
DynamicProgram();
solve();
printf("%d\n",ans);
return 0;
}
廣義容斥原理
基礎概念
用語言描述,容斥原理求的是不滿足任何性質的方案數,我們通過計算所有至少滿足\(k\)個性質的方案數之和來計算。
同樣的,我們可以通過計算所有至少滿足\(k\)個性質的方案數之和來計算恰好滿足\(k\)個性質的方案數。這樣的容斥方法我們稱之為廣義容斥原理。
容斥方法
首先,我們設\(\alpha(k)\)代表所有至少滿足\(k\)的性質的方案數之和。
也就是說:
我們發現\(\alpha(k)\)將具有\(p(p\geq k)\)個性質的元素計算了\(\binom{k}{p}\)次。
假設\(\beta(k)\)代表恰好具有\(k\)個元素的方案數,則有遞推公式如下:
組合意義就是我們把多算的那些方案數都減掉就可以了。
但是這樣計算我們需要求出每一個\(\beta\)的值,時間復雜度是\(O(n^2)\)的。
我們有一個更好的計算式:
這樣就可以\(O(n)\)計算單個\(\beta\)的值了,我們現在來證明這個公式的正確性。
我們考慮具有\(t\)個的性質的元素被計算了幾次:
\(1.\) 如果\(t<k\),則該元素一次都沒有被計算過。
\(2.\) 如果\(t=k\),則該元素恰好被計算了一次。
\(3.\) 如果\(t>k\),\(......\)真復雜
那么現在我們要證明,當\(t>k\)時,具有\(t\)個性質的元素沒有被計算過。
根據上面提到的\(\alpha\)中多算的次數,我們容易得知具有\(t\)個性質的元素被計算了這么多次:
利用三項式系數恆等式,我們對此進行化簡:
根據二項式定理,我們構造:
令\(x=1\),我們得知:
所以,當\(t>k\)時,具有\(t\)個性質的元素沒有被計算過。那么我們就可以愉快地容斥了。
集合計數
Description
一個有N個元素的集合有2 ^ N個不同子集(包含空集),現在要在這2^N個集合中取出若干集合(至少一個),使得它們的交集的元素個數為K,求取法的方案數,答案模1000000007。(是質數喔~)
Input Format
一行兩個整數N,K。
Output Format
一行為答案。
Sample Input
3 2
Sample Output
6
解析
當這些集合具有一個元素的交集的時候,我們就認為這種方案具有一個性質,那么我們要求的就是具有\(k\)個性質的元素個數,是廣義容斥原理計算的對象。
那么我們現在只需考慮如何計算至少具有\(k\)個性質的元素個數即可。
首先,我們只要強制選\(k\)個元素,使他們成為交集的一部分,然后剩下的隨便選,這樣的方案就一定具有\(k\)個元素以上的交集。
那么就可以這樣計算了:
組合意義:首先我們選\(k\)個元素有\(\binom{n}{k}\)種方案,剩下的元素可選可不選,可以組成\(2^{n-k}\)個子集,每個子集可選可不選,就有\(2^{2^{n-k}}\)種方案了。
直接使用公式計算\(\beta(k)\)的值即可。
\(Code:\)
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6+20 , Mod = 1e9+7;
int n,k,alpha[N],fac[N],inv[N],Pow[N],ans;
inline int add(int a,int b) { return a + b >= Mod ? a + b - Mod : a + b; }
inline int mul(int a,int b) { return 1LL * a * b % Mod; };
inline int sub(int a,int b) { return a - b < 0 ? a - b + Mod : a - b; }
inline void Add(int &a,int b) { a = add( a , b ); }
inline void Mul(int &a,int b) { a = mul( a , b ); }
inline void Sub(int &a,int b) { a = sub( a , b ); }
inline int quickpow(int a,int b) { int res = 1; for (;b;Mul(a,a),b>>=1) if ( 1 & b ) Mul(res,a); return res; }
inline void init(void)
{
fac[0] = inv[0] = Pow[0] = 1;
for (int i=1;i<=n;i++)
fac[i] = mul( fac[i-1] , i ) , Pow[i] = Pow[i-1] * 2LL % (Mod-1);
inv[n] = quickpow( fac[n] , Mod-2 );
for (int i=n-1;i>=1;i--)
inv[i] = mul( inv[i+1] , i+1 );
}
inline int C(int n,int m) { return mul( fac[n] , mul( inv[m] , inv[n-m] ) ); }
inline void solve(void)
{
for (int i=0;i<=n;i++)
alpha[i] = mul( C(n,i) , quickpow( 2 , Pow[n-i] ) );
for (int i=k;i<=n;i++)
if ( ( i - k ) & 1 ) Sub( ans , mul( C(i,k) , alpha[i] ) );
else Add( ans , mul( C(i,k) , alpha[i] ) );
}
int main(void)
{
scanf("%d%d",&n,&k);
init();
solve();
printf("%d\n",ans);
return 0;
}
已經沒有什么好害怕的了
Description
有糖果和葯片各\(n\)個。糖果\(i\)有能量\(a_i\),葯片\(i\)有能量\(b_i\)。
你需要將葯片和糖果兩兩配對,求有多少種方案滿足糖果比葯片能量大的組數減去葯片比糖果能量大的組數恰好為\(k\)。
保證所有的能量兩兩不同,答案對\(10^9+9\)取模。
Input Format
第一行兩個整數\(n,k\)。
第二行\(n\)個整數,表示糖果的能量。
第三行\(n\)個整數,表示葯片的能量。
Output Format
輸出一行一個整數,表示方案數。
Sample Input
4 2
5 35 15 45
40 20 10 30
Sample Output
4
解析
首先,我們設糖果能量大於葯片能量有\(x\)組,葯片能量大於糖果能量有\(y\)組,那么符合題意的方案應該滿足:
很容易得知,\(n+k\)為奇數的時候無解,反之\(x=\frac{n+k}{2}\)。
那么我們假設存在一組糖果能量大於葯片能量就具有一個性質,原問題就是要求具有\(\frac{n+k}{2}\)個性質的方案數。
套用廣義容斥原理的方法,我們需要計算具有至少\(k\)個性質的方案數。
我們需要把糖果盒葯片先排一下序,然后考慮\(dp\)計算方案。設\(f[i][j]\)代表前\(i\)個糖果,有\(j\)個糖果的能量大於葯片的方案數,容易寫出狀態轉移方程:
其中\(cnt[i]\)代表比第\(i\)個糖果能量小的葯片的數量。
容易得知:\(\alpha(k)=f[n][k]\times(n-k)!\),套用廣義容斥原理計算\(\beta\)即可。
\(Code:\)
#include <bits/stdc++.h>
using namespace std;
const int N = 2020 , Mod = 1e9+9;
int n,k,a[N],b[N],C[N][N],fac[N];
int alpha[N],f[N][N],cnt[N],ans;
inline int add(int a,int b) { return a + b >= Mod ? a + b - Mod : a + b; }
inline int mul(int a,int b) { return 1LL * a * b % Mod; };
inline int sub(int a,int b) { return a - b < 0 ? a - b + Mod : a - b; }
inline void Add(int &a,int b) { a = add( a , b ); }
inline void Mul(int &a,int b) { a = mul( a , b ); }
inline void Sub(int &a,int b) { a = sub( a , b ); }
inline int quickpow(int a,int b) { int res = 1; for (;b;Mul(a,a),b>>=1) if ( 1 & b ) Mul(res,a); return res; }
inline void input(void)
{
scanf("%d%d",&n,&k);
for (int i=1;i<=n;i++) scanf("%d",&a[i]);
for (int i=1;i<=n;i++) scanf("%d",&b[i]);
}
inline void init(void)
{
int p = 0; fac[0] = 1;
for (int i=1;i<=n;i++)
{
fac[i] = mul( fac[i-1] , i );
while ( p < n && b[p+1] < a[i] ) p++;
cnt[i] = p;
}
C[0][0] = C[1][0] = C[1][1] = 1;
for (int i=2;i<=n;i++)
{
C[i][0] = C[i][i] = 1;
for (int j=1;j<i;j++)
C[i][j] = add( C[i-1][j-1] , C[i-1][j] );
}
}
inline void DynamicProgram(void)
{
for (int i=0;i<=n;i++) f[i][0] = 1;
for (int i=1;i<=n;i++)
for (int j=1;j<=i;j++)
f[i][j] = add( f[i-1][j] , mul( f[i-1][j-1] , max( cnt[i]-j+1 , 0 ) ) );
}
inline void solve(void)
{
k = ( n + k ) / 2;
for (int i=1;i<=n;i++)
alpha[i] = mul( f[n][i] , fac[n-i] );
for (int i=k;i<=n;i++)
if ( ( i - k ) & 1 ) Sub( ans , mul( C[i][k] , alpha[i] ) );
else Add( ans , mul( C[i][k] , alpha[i] ) );
}
int main(void)
{
input();
if ( ( n + k ) & 1 )
return puts("0") , 0;
sort( a+1 , a+n+1 );
sort( b+1 , b+n+1 );
init();
DynamicProgram();
solve();
printf("%d\n",ans);
return 0;
}
<后記>