終於講到反演定理了,反演定理這種東西記一下公式就好了,反正我是證明不出來的~(~o ̄▽ ̄)~o
首先,著名的反演公式
我先簡單的寫一下o( ̄ヘ ̄*o)
比如下面這個公式
f(n) = g(1) + g(2) + g(3) + ... + g(n)
如果你知道g(x),藍后你就可以知道f(n)了
如果我知道f(x),我想求g(n)怎么辦
這個時候,就有反演定理了
反演定理可以輕松的把上面的公式變為
g(n) = f(1) + f(2) + f(3) + ... + f(n)
當然,我寫的只是個形式,怎么可能這么簡單。◕‿◕。
其實每一項再乘一個未知的函數就對了,但是這個函數我們不知道(不用擔心,數學家已經幫我們解決了,我們直接用就可以了)
反演公式登場( >ω<)
c和d是兩個跟n和r有關的函數
根據用法不同,c和d是不同的
一般數學家會先隨便弄c函數
然后經過復雜的計算和證明,得到d函數
然后公式就可以套用了
正片開始
二項式反演公式
那個括號起來的就是組合數,我記得組合數那章我有說過
二項式反演也就是記住這個公式就算結束了
然后我們開始實戰(/ω\)
容斥那章講過的全錯排(裝錯信封問題)
hdu 1465
http://acm.hdu.edu.cn/showproblem.php?pid=1465
設g(i)表示正好有i封信裝錯信封
那么全部的C(n, i)*g(i)加起來正好就是所有裝信的情況,總共n!種情況
n! = Σ C(n, i)*g(i) (i從0到n)
那么f(n) = n!,所以f(x) = x!
那么我們要求g(n)
根據公式
g(n) = Σ (-1)^(n-i) * C(n, i) * f(i) (i從0到n)
那么就可以計算啦~\(≧▽≦)/~
AC代碼:

1 #include<cstdio> 2 typedef long long LL; 3 int n, flag; 4 LL fac[25]; 5 LL ans; 6 void init(){ 7 fac[0] = 1; 8 for(int i = 1; i <= 20; i ++) fac[i] = fac[i-1] * i; 9 } 10 int main(){ 11 init(); 12 while(~scanf("%d", &n)){ 13 ans = 0; 14 flag = n & 1 ? -1 : 1;//起始符號 15 for(int i = 0; i <= n; i ++){ 16 ans += flag * fac[n] / fac[n-i]; 17 flag = -flag; 18 } 19 printf("%I64d\n", ans); 20 } 21 }
是不是很好用但是不容易想到T_T
這也沒有辦法
再來一題吧
還是容斥那一章講過的題目的
UVALive 7040
https://icpcarchive.ecs.baylor.edu/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=5052
題意:給n盆花塗色,從m種顏色中選取k種顏色塗,保證正好用上k種顏色,你必須用上這k種顏色去塗滿n個相鄰的花,並且要求相鄰花的顏色不同,求方案數。
(1 ≤ n, m ≤ 1e9 , 1 ≤ k ≤ 1e6 , k ≤ n, m)
首先,用k種顏色塗花,假如不考慮全部用上,那么總的方案數是多少
第一盆花有k種顏色選擇,之后的花因為不能跟前一盆花的顏色相同,所以有k-1種選擇
於是總方案數為k*(k-1)^(n-1)
我們設必須用 i 種顏色兩兩不相鄰的塗花的方案數為 g(i)
那么
k*(k-1)^(n-1) = Σ C(k, i)*g(i) (i從1到k)
令f(k) = k*(k-1)^(n-1),那么f(x) = x*(x-1)^(n-1)
二項式反演公式出現了
所以g(k) = Σ (-1)^(k-i) * C(k, i) * f(i) (i從1到k)
最終的答案就是C(m, k) * g(k)
(這里m有1e9,C(m, k)直接用for循環算,直接for循環從m*(m-1)*...*(m-k+1)再乘k的階乘的逆元)
AC代碼:

1 #include<cstdio> 2 typedef long long LL; 3 const int N = 1000000 + 5; 4 const int MOD = (int)1e9 + 7; 5 int F[N], Finv[N], inv[N]; 6 LL pow_mod(LL a, LL b, LL p){ 7 LL ret = 1; 8 while(b){ 9 if(b & 1) ret = (ret * a) % p; 10 a = (a * a) % p; 11 b >>= 1; 12 } 13 return ret; 14 } 15 void init(){ 16 inv[1] = 1; 17 for(int i = 2; i < N; i ++){ 18 inv[i] = (MOD - MOD / i) * 1ll * inv[MOD % i] % MOD; 19 } 20 F[0] = Finv[0] = 1; 21 for(int i = 1; i < N; i ++){ 22 F[i] = F[i-1] * 1ll * i % MOD; 23 Finv[i] = Finv[i-1] * 1ll * inv[i] % MOD; 24 } 25 } 26 int comb(int n, int m){ 27 if(m < 0 || m > n) return 0; 28 return F[n] * 1ll * Finv[n - m] % MOD * Finv[m] % MOD; 29 } 30 int main(){ 31 init(); 32 int T, n, m, k, ans, flag, temp; 33 scanf("%d", &T); 34 for(int cas = 1; cas <= T; cas ++){ 35 scanf("%d%d%d", &n, &m, &k); 36 ans = 0; 37 flag = (k & 1) ? 1 : -1; 38 //計算g(k) 39 for(int i = 1; i <= k; i ++){ 40 ans = (ans + 1ll * flag * comb(k, i) * i % MOD * pow_mod(i-1, n-1, MOD) % MOD) % MOD; 41 flag = -flag; 42 } 43 //接下來計算C(m, k) 44 temp = Finv[k]; 45 for(int i = 1; i <= k; i ++){ 46 temp = 1ll * temp * (m-k+i) % MOD; 47 } 48 ans = ((1ll * ans * temp) % MOD + MOD) % MOD; 49 printf("Case #%d: %d\n", cas, ans); 50 } 51 }
做完兩題之后就感覺二項式反演變得容易了,遇到題目還是要多想( ̄▽ ̄")
等等。。。做完兩題的我突然發現二項式反演和容斥推倒出來的公式總是一樣的。。。。。。(為什么有種被騙的感覺T_T)