『容斥原理和广义容斥原理』

<更新提示>

<第一次更新>


<正文>

容斥原理

基础概念

我们假设有全集\(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)\]


<后记>

猜你喜欢

转载自www.cnblogs.com/Parsnip/p/11530658.html
今日推荐