組合數+二項式反演(容斥)-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);
}
}