<更新提示>更新提示>
<第一次更新>第一次更新>
<正文>正文>
容斥原理
基础概念
我们假设有全集\(S\),以及\(n\)个集合\(A_1,A_2,...,A_n\),每个集合\(A_i\)中的元素具有性质\(P_i\),现在我们要求不具有任何性质的集合大小,也就是元素个数,则具有如下的计算式:
\[\left |\bigcap_{i=1}^n\overline{A_i}\right|=|S|+\sum_{T\subseteq\{1,2,...,n\},T\not =\emptyset}(-1)^{|T|}\left | \bigcap_{i\in T}A_i \right |\]
当然,我们还具有另一种形式:
\[\left | \bigcup_{i=1}^nA_i \right |=\sum_{T\subseteq \{1,2,...,n\}}(-1)^{|T|-1}\left | \bigcap_{i\in T} A_i \right |\]
这两种形式可以简单地互相转换得到,本质相同。
如何理解其组合意义,我们可以这样解释:
形式\(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\)个人落选的方案数之和,显然,方案数即为:
\[\binom{k}{m}(k-m)^n\]
那么根据容斥原理,答案即为:
\[k^n+\sum_{i=1}^m(-1)^i\binom{k}{i}(k-i)^n\]
注意,模数是\(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\)个人拿着自己同种糖的方案数,可以直接转移:
\[f[i][j]=\sum_{k=0}^{min(j,cnt[i])}f[i-1][j-k]\times\binom{cnt[i]}{k}\times cnt[i]^{\underline{k}}\]
组合意义:前\(i-1\)种糖中已经有\(j-k\)个人拿着自己同种的糖了,现在第\(i\)种糖这样的人要有\(k\)个,方案就是\(cnt[i]\)个人中选\(k\)个人,第一个人有\(cnt[i]\)种选择选到同种糖,第二个人有\(cnt[i]-1\)种可能选到同种糖\(...\)
设\(m\)为颜色总数,然后容斥:
\[ans=\sum_{i=0}^m(-1)^i\times f[m][i]\times (n-i)!\]
由于我们一开始把同种糖的每一个都看作本质不同的,所以最后要除掉每一种颜色出现次数的阶乘。
\(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(0)=|S|\\ \ \\ \alpha(1)=\sum_{i}|A_i|\\ \ \\ \alpha(2)=\sum_{i,j}|A_i\cap A_j| \\ ...\\ \ \\ \alpha(k)=\sum_{T\subseteq\{1,2,...,n\},|T|=k}\left | \bigcap_{i\in T}A_i \right |\]
我们发现\(\alpha(k)\)将具有\(p(p\geq k)\)个性质的元素计算了\(\binom{k}{p}\)次。
假设\(\beta(k)\)代表恰好具有\(k\)个元素的方案数,则有递推公式如下:
\[\beta(k)=\alpha(k)-\sum_{i=k+1}^n\binom{i}{k}\beta(i)\]
<后记>后记>