1. 程式人生 > >UVAlive-7040 color(組合數學,二項式反演)

UVAlive-7040 color(組合數學,二項式反演)

main closed times char 數學 mod 處理 pow sed

鏈接:vjudge

題目大意:有一排方格共 $n$ 個,現在有 $m$ 種顏色,要給這些方格染色,要求相鄰兩個格子的顏色不能相同。現在問恰好用了 $k$ 種顏色的合法方案數。答案對 $10^9+7$ 取模。$T$ 組數據。

$1\le T\le 300,1\le n,m\le 10^9,1\le k\le 10^6,k\le \min(n,m)$。大多數數據中 $k$ 很小。(smg啊……)


經典的二項式反演例題。

我們令 $f(x)$ 為一共有 $x$ 種顏色,恰好用了 $x$ 種顏色的方案數。

答案就是 ${m\choose k}f(k)$。因為任意選 $k$ 種顏色方案數是一樣的。

這……似乎不太好算?

我們再令 $g(x)$ 為一共有 $x$ 種顏色,用了至多 $x$ 種顏色的方案數。

這個就不難算了。第一個格子可以隨便填,就是 $x$ 種。後面的格子只要不和上一個顏色相同就行了,就是 $x-1$ 種。

乘法原理一下:$g(x)=x(x-1)^{n-1}$。$x=0$ 時這個式子是 $0$。

但是要註意,$x=n=1$ 時我們這樣計算是 $0$,但是實際上是 $1$。為什麽?$1\times 0^0$。所以我們要把 $0^0$ 看做 $1$,或者直接特判掉。

(但是不特判也能過,數據太水,這多組數據沒用吧)

我們來想一想 $f$ 和 $g$ 有什麽關系。很容易發現:$g(x)=\sum\limits^x_{i=0}{x\choose i}f(i)$。因為 $x$ 種顏色中隨便選 $i$ 種都可以。

標準二項式反演形式。$f(x)=\sum\limits^x_{i=0}(-1)^{x-i}{x\choose i}g(i)$。

因為 $x\le 10^6$,所以階乘和逆元都可以預處理,組合數就可以 $O(1)$ 了。此時 $f(x)$ 就可以 $O(x\log n)$ 算了。

現在問題就是算 $m\choose k$ 了。$m$ 達到了驚人的 $10^9$,模數又是個大數……怎麽辦?

我們發現 $m\choose k$ 可以表示成一種不常用的形式:$\frac{m(m-1)(m-2)...(m-k+1)}{k!}$。

此時分母是預處理過的,分子可以 $O(k)$ 算。這就完事了。

總時間復雜度 $O(Tk\log n)$。因為大多數數據中 $k$ 很小,所以可以跑過。

……這數據範圍我給滿分……

代碼:

技術分享圖片
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1000100,mod=1000000007;
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline int read(){
    char ch=getchar();int x=0,f=0;
    while(ch<0 || ch>9) f|=ch==-,ch=getchar();
    while(ch>=0 && ch<=9) x=x*10+ch-0,ch=getchar();
    return f?-x:x;
}
int t,n,m,k,fac[maxn],inv[maxn],invfac[maxn];
void init(){    //預處理階乘,逆元,階乘的逆元
    fac[0]=fac[1]=inv[1]=invfac[0]=invfac[1]=1;
    FOR(i,2,1000000){
        fac[i]=1ll*fac[i-1]*i%mod;
        inv[i]=mod-1ll*(mod/i)*inv[mod%i]%mod;
        invfac[i]=1ll*invfac[i-1]*inv[i]%mod;
    }
}
int C(int n,int m){
    if(n<=1000000) return 1ll*fac[n]*invfac[m]%mod*invfac[n-m]%mod;    //n,m很小,可以直接算
    int ans=invfac[m];    //分母是m的階乘
    ROF(i,n,n-m+1) ans=1ll*ans*i%mod;    //暴力乘上分子
    return ans;
}
int qpow(int a,int b){    //快速冪
    int ans=1;
    for(;b;b>>=1,a=1ll*a*a%mod) if(b&1) ans=1ll*ans*a%mod;
    return ans;
}
int g(int x){
    if(x==1 && n==1) return 1;    //特判掉x=n=1
    return 1ll*x*qpow(x-1,n-1)%mod;
}
int f(int x){
    int ans=0;
    FOR(i,0,x){
        int v=1ll*C(x,i)*g(i)%mod;
        if((x-i)&1) ans=(ans-v+mod)%mod;    //(-1)^(x-i)
        else ans=(ans+v)%mod;
    }
    return ans;
}
int main(){
    init();
    t=read();
    FOR(tt,1,t){
        n=read();m=read();k=read();
        printf("Case #%d: %d\n",tt,int(1ll*C(m,k)*f(k)%mod));    //記得乘上C(m,k)
    }
}
二項式反演

UVAlive-7040 color(組合數學,二項式反演)