終於講到反演定理了,反演定理這種東西記一下公式就行了,反正我是證實不出來的~(~o ̄▽ ̄)~ophp
首先,著名的反演公式ide
我先簡單的寫一下o( ̄ヘ ̄*o)函數
好比下面這個公式spa
f(n) = g(1) + g(2) + g(3) + ... + g(n)3d
若是你知道g(x),藍後你就能夠知道f(n)了code
若是我知道f(x),我想求g(n)怎麼辦blog
這個時候,就有反演定理了數學
反演定理能夠輕鬆的把上面的公式變爲it
g(n) = f(1) + f(2) + f(3) + ... + f(n)io
固然,我寫的只是個形式,怎麼可能這麼簡單。◕‿◕。
其實每一項再乘一個未知的函數就對了,可是這個函數咱們不知道(不用擔憂,數學家已經幫咱們解決了,咱們直接用就能夠了)
反演公式登場( >ω<)
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)