1. 程式人生 > >UVALive 7040 Color (容斥原理 + 組合數學遞推公式 + 求逆元 + 基礎數論)

UVALive 7040 Color (容斥原理 + 組合數學遞推公式 + 求逆元 + 基礎數論)

傳送門
英文題目:

Recently, Mr. Big recieved n owers from his fans. He wants to recolor those owers with m colors. The
owers are put in a line. It is not allowed to color any adjacent owers with the same color. Flowers i and
i + 1 are said to be adjacent for every i, 1 ≤ i < n. Mr. Big also wants the total number of different
colors of the n owers being exactly k.
Two ways are considered different if and only if there is at least one ower being colored with different
colors.

插圖:

題目描述的插圖

題目大意:
首先有T組資料,每組資料有 3 個數 n, m, k,分別代表一共有 n 個方格,m種顏色,而我們要 恰好(注意是恰好) 使用 k 中顏色對這些方格進行塗色,並且保證的是每兩個相鄰的方格的顏色必須是不一樣的。

解題思路:
首先拿過這個題來 不要急著就是說一眼就做出來,所以我們先對它分析一下,我們要從 m 種顏色中選出 k 個進行塗色 所以用的方法數就是 C(m,k),然後對這 n 個方格塗色,第一個 有 k 種選擇, 而後邊的 n-1 個方格中只有 k-1 種選擇,所以 就有公式

S=C(m,k)k(k1)n1
然後這並不是最後的結果,這是 所選的顏色不超過 k 種的方法數,而不是 恰好 用 k 種顏色的,然後就可以用容斥原理來求了,假設集合 Ai 表示 i 號顏色不被選,所以 我們要求的結果 a
ns=S(A1A2...An)

A1 ⋃ A2 ⋃… ⋃ An 就得通過容斥原理來做啦。。。
根據上面我們可以求出從 k 種顏色種選 k-i個,剩下的就是 n-1個方格中有 k-i-1中選擇。所以這個公式就是:
C(k,ki)(ki)(ki1)n1
然後考慮的就是奇數加 偶數減就行了。然後再注意一下細節問題:
1.怎麼樣求C(m,k),這個就是用到 遞推公式了首先給出一個公式
C(x,i)=C(x,i1)ni+1i
這個公式很好推導:
C(n,i)=n!i!(ni)!=n!i(i1)!(ni+1)!/(ni+1)=n!(i1)!(n
i+1)!
ni+1i
=C(x,i1)ni+1i

所以根據這個公式我們可以打個表就能算 C(m,k)和 C(k,i)了
2.求逆元的問題。因為 C(x,i)=C(x,i-1)*(x-i+1)/i可以通過擴充套件歐幾里得演算法來算,但是要記得的是要用一個數組儲存一下 每一個的逆元,要不然每次呼叫函式的話 會超時。
3.就是容斥的詳細過程了。記住一點 奇數加偶數減就行
接下來就是編寫程式了,詳見我的程式碼。

My Code:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>

using namespace std;
const int MOD = 1e9+7;
const int MAXN = 1e6+5;
typedef long long LL;
LL Scan()///輸入外掛
{
    LL res=0,ch,flag=0;
    if((ch=getchar())=='-')
        flag=1;
    else if(ch>='0'&&ch<='9')
        res=ch-'0';
    while((ch=getchar())>='0'&&ch<='9')
        res=res*10+ch-'0';
    return flag?-res:res;
}

void Out(LL a)///輸出外掛
{
    if(a>9)
        Out(a/10);
    putchar(a%10+'0');
}

///快速冪
LL quick_mod(LL a, LL b)
{
    LL ans = 1;
    while(b)
    {
        if(b & 1)
            ans = ans*a%MOD;
        b>>=1;
        a = a*a%MOD;
    }
    return ans;
}
///求逆元
void Exgcd(LL a, LL b, LL &x, LL &y)
{
    if(b == 0)
    {
        x = 1;
        y = 0;
        return;
    }
    LL x1, y1;
    Exgcd(b, a%b, x1, y1);
    x = y1;
    y = x1 - (a/b)*y1;
}
LL Inv[MAXN];///放逆元的陣列
///得到逆元
void Get_Inv()
{
    Inv[1] = 1;
    for(int i=2; i<MAXN; i++)
    {
        LL y;
        Exgcd(i, MOD, Inv[i], y);
        Inv[i] = (Inv[i]%MOD+MOD)%MOD;
    }
}
/**     得到組合數,通過遞推公式
        C(m,i) = C(m,i-1)*(m-i+1)/i;    **/
LL cm[MAXN], ck[MAXN];///分別是m的組合數 和 k 的組合數
LL n, m, k;

void Get_Fac()
{
    cm[0] = ck[0] = 1;
    for(int i=1; i<=k; i++)
    {
        cm[i] = (cm[i-1]%MOD * (m-i+1)%MOD * Inv[i]%MOD) % MOD;
        ck[i] = (ck[i-1]%MOD * (k-i+1)%MOD * Inv[i]%MOD) % MOD;
    }
}
/**
11
1000000000 1000000000 1000000
**/
int main()
{
    Get_Inv();
    int T;
    T = Scan();
    for(int cas=1; cas<=T; cas++)
    {
        n = Scan();
        m = Scan();
        k = Scan();
        Get_Fac();
        LL ret = 1;
        LL ans = 0;
        for(LL i=k; i>=1; i--)
        {
            ans = (ans+ret*i*ck[i]%MOD*quick_mod(i-1,n-1)%MOD+MOD)%MOD;
            ret = -ret;
            //cout<<ans<<" ";
        }
        ans = ans * cm[k] % MOD;
        printf("Case #%d: %lld\n",cas,ans);
    }
    return 0;
}