1. 程式人生 > >組合數+二項式反演(容斥)-UVALive

組合數+二項式反演(容斥)-UVALive

題意:t組資料,每組給定n,m,k。有n個格子,m種顏色,要求把每個格子塗上顏色且正好適用k種顏色且相鄰的格子顏色不同,求一共有多少種方案,結果對1e9+7取餘。

二項式反演(重點)

這裡寫圖片描述

設g(i)表示正好有i個顏色塗格子

那麼全部的C(k, i)*g(i)加起來正好就是所有塗格子情況,總共k(k-1)^(n-1)種情況

k(k-1)^(n-1) = Σ C(k, i)*g(i) (i從0到n)

那麼f(k) = k(k-1)^(n-1),所以f(x) = x(x-1)^(n-1)

那麼我們要求g(k)

根據公式:g(k) = Σ (-1)^(k-i) * C(k, i) * f(i) (i從0到n)

題解:

首先可以將m 與後面的討論分離。從m 種顏色中取出k 種顏色塗色,取色部分有C(m, k) 種情況;

然後通過嘗試可以發現,第一個有k種選擇,第二個因不能與第一個相同,只有(k-1) 種選擇,第三個也只需與第二個不同,也有(k-1) 種選擇。總的情況數為k ×(k-1)^(n-1)。但這僅保證了相鄰顏色不同,總顏色數不超過k種,並沒有保證恰好出現k種顏色;

接著就是一個容斥問題,上述計算方法中包含了只含有2、3、…、(k-1)種顏色的情況,需要通過容斥原理去除。假設出現p (2 <= p <= k-1)種顏色,從k種顏色中選取p種進行塗色,方案數為C(k,p) × p × (p-1)^(n-1);

綜上,最後的總方案數為C(m,k) × ( k × (k-1)^(n-1) + ∑((-1)^p × C(k, p) × p × (p-1)^(n-1) ) (2 <= p <= k-1);

最後,需要注意1 ≤ n, m ≤10^9,在進行指數運算時,需要使用快速冪。對於組合數,只需要計算C(m,k)和C(k,p) (1 <= p <= k),可以採用遞推法,即C[x,i] = C[x, i-1] * (n-i+1) / i,因為要取模,所以需要用到i的逆元。

注意在總方案數的公式中p是大於等於2的,但是要特別注意當n=1的時候的情況,此時k只能為1,ans=m,如果k不為1,ans=0。

#include<bits/stdc++.h>
#define ull unsigned long long
#define ll long long
const int mod =1000000007;
using namespace std;
const int maxn=1e6+5;
int a[maxn];
int t;
ll n,m,k;
ll exgcd(ll a,ll b,ll &x,ll &y)
{
    if(b==0)
    {
        x=1,y=0;
        return a;
    }
    else
    {
        ll q=exgcd(b,a%b,y,x);
        y=y-x*(a/b);
        return q;
    }
}
ll niyuan(ll a,ll n)//逆元
{
    ll x,y;
    ll d=exgcd(a,n,x,y);
    if(d==1)
        return (x%n+n)%n;
    else
        return -1;
}
ll qmod(ll n,ll m,ll mod)//快速冪
{
    ll ans=1;
    while(m)
    {
        if(m&1)ans=(ans*n)%mod;
        n=(n*n)%mod;
        m>>=1;
    }
    return ans%mod;
}
ll cm[maxn],ck[maxn],inv[maxn];
void get_inv()//打表
{
     for(int i=1;i<maxn;i++)
        inv[i]=niyuan(i,mod);
}
void init()//打表
{
    cm[0]=1,ck[0]=1;
    for(int i=1;i<=k;i++)
    {
        cm[i]=((cm[i-1]%mod*(m-i+1)%mod)*inv[i])%mod;
        ck[i]=((ck[i-1]%mod*(k-i+1)%mod)*inv[i])%mod;
    }
}
int main()
{

    get_inv();
    scanf("%d",&t);
    int ca=1;
    while(t--)
    {
        scanf("%lld%lld%lld",&n,&m,&k);
        init();
        ll ret;
        ll ans=0;
        for(ll i=k; i>=1; i--)
        {
            if((k-i)%2)
                ret=-1;
            else
                ret=1;
   ans=(ans+ret*i%mod*ck[i]%mod*qmod(i-1,n-1,mod)%mod+mod)%mod;
     //二項式反演      

        }
        ans=(ans*cm[k])%mod;

        printf("Case #%d: %lld\n",ca++,ans);

    }
}