ACM數論之旅17---反演定理 第一回 二項式反演(神說要有光 於是就有了光(´・ω・`))


 

終於講到反演定理了,反演定理這種東西記一下公式就好了,反正我是證明不出來的~(~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)

 

當然,我寫的只是個形式,怎么可能這么簡單。◕‿◕。

 

其實每一項再乘一個未知的函數就對了,但是這個函數我們不知道(不用擔心,數學家已經幫我們解決了,我們直接用就可以了)

 

 

 

 

 

 

反演公式登場( >ω<)

 

反演定理1

 

c和d是兩個跟n和r有關的函數

根據用法不同,c和d是不同的

一般數學家會先隨便弄c函數

然后經過復雜的計算和證明,得到d函數

然后公式就可以套用了

 

 

 

 

 

 

 

 

 

 

 

正片開始

 

二項式反演公式

 

二項式反演1

 

那個括號起來的就是組合數,我記得組合數那章我有說過

組合數4

 

 

二項式反演也就是記住這個公式就算結束了

 

然后我們開始實戰(/ω\)

 

 

 

 

 

 

 

 

 

容斥那章講過的全錯排(裝錯信封問題)

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 }
View Code

 

 

 

 

 

 

 

 

 

是不是很好用但是不容易想到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 }
View Code

 

 

 

 

 

 

 

 

 

 

 

 

做完兩題之后就感覺二項式反演變得容易了,遇到題目還是要多想( ̄▽ ̄")

等等。。。做完兩題的我突然發現二項式反演和容斥推倒出來的公式總是一樣的。。。。。。(為什么有種被騙的感覺T_T)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM