ACM數論之旅13---容斥原理(一切都是命運石之門的選擇(=゚ω゚)ノ)

 

 容斥原理我初中就聽老師說過了,不知道大家有沒有聽過(/≧▽≦)/php

 

百度百科說:數組

在計數時,必須注意沒有重複,沒有遺漏。ide

爲了使重疊部分不被重複計算,人們研究出一種新的計數方法。函數

這種方法的基本思想是:先不考慮重疊的狀況,把包含於某內容中的全部對象的數目先計算出來,而後再把計數時重複計算的數目排斥出去,使得計算的結果既無遺漏又無重複。spa

這種計數的方法稱爲容斥原理。3d

 

 

好標準的說法(#-.-)code

 

 

那我舉個簡單的例子對象

 

兩個集合的容斥原理: 設A, B是兩個有限集合blog

容斥原理1

那麼遞歸

|A + B| = |A| + |B| - |AB|

|A|表示A集合中的元素個數

 

三個集合的容斥原理: 設A, B, C是三個有限集合

容斥原理2

那麼

|A + B + C| = |A| + |B| + |C| - |AB| - |AC| - |BC| + |ABC|

 

 

 

 

這就叫容斥原理

 

 

 

 

 

接下來直接作例題了

 

 

 

 

 

 

全錯排(裝錯信封問題)

hdu 1465

http://acm.hdu.edu.cn/showproblem.php?pid=1465

 

n封信對應n個信封

求剛好所有裝錯了信封的方案數

 

原本全錯排是有本身的一個公式的,叫全錯排公式(跟容斥不要緊)

那我順便來說講全錯排( >ω<)

要裝第i封信的時候,先把前i-1個信全裝錯信封,而後隨便選其中一個與第i封信交換,有i-1種選法

那麼dp[i] = (i-1) * dp[i-1]

可是還有一種狀況

要裝第i封信的時候,先從i-1封信中任選i-2個信把他們全裝錯信封,而後把剩下的那個信與第i個交換,從i-1封信中任選i-2個信有i-1種選法

那麼dp[i] = (i-1) * dp[i-2]

兩個式子聯合起來

就是那麼dp[i] = (i-1) * (dp[i-1] + dp[i-2])

這就是全錯排公式,遞推,遞歸均可以作

 

全錯排遞推AC代碼:

 1 #include<cstdio>
 2 typedef long long LL;
 3 int n;
 4 LL dp[25];
 5 void init(){
 6     dp[1] = 0;
 7     dp[2] = 1;
 8     for(int i = 3; i <= 20; i ++){
 9         dp[i] = (i-1) * (dp[i-1] + dp[i-2]);
10     }
11 }
12 int main(){
13     init();
14     while(~scanf("%d", &n)){
15         printf("%I64d\n", dp[n]);
16     }
17 }
View Code

 

 

 

 

那麼這題容斥怎麼作呢?

 

首先,全部裝信的總數是n!

(在n中任選一個信封放進一封信,而後在剩下的n-1中任選一個信封放進一封信,以此類推,因此是n*(n-1)*(n-2)... = n!)

 假設

A1表示1封信裝對信封,數量是(n-1)! (只有n-1個位置能夠亂放)

A2表示2封信裝對信封,數量是(n-2)! (只有n-2個位置能夠亂放)

...

An表示n封信裝對信封,數量是1 

 

那麼這題的答案就是

n! - C(n, 1)*|A1| + C(n, 2)*|A2| - C(n, 3)*|A3| + ... + (-1)^n * C(n, n)*|A4|

 

把C(n, m)用

組合數1代入式子

化簡

n! - n! / 1! + n! / 2! - n! / 3! + ... + (-1)^n * n! / n!

提取n!

n!(1 - 1/1! + 1/2! - 1/3! + ... + (-1)^n * 1/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 = fac[n];
14         flag = -1;//容斥的符號變化
15         for(int i = 1; i <= n; i ++){
16             ans += flag * fac[n] / fac[i];
17             flag = -flag;  
18         }
19         printf("%I64d\n", ans);
20     }
21 }
View Code

 

 

 

 

 

 

 

 

 

 

第二例題:

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)

由於題目問必須用上k種顏色

這裏麪包含了只用k-1種顏色的狀況,應該減掉全部用k-1種的狀況

減掉的東西里面,這裏麪包含了只用k-2種顏色的狀況,應該加回來

...

反反覆覆,最後就得出答案了(這算是解釋嗎。。。)

 

最後答案就是

C(m,k) * ( k * (k-1)^(n-1) + [∑((-1)^i * C(k, k - i) * (k-i) * (k-i-1)^(n-1)) ] )    (1 <= i <= k-1)    紅色表示容斥部分

(這裏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 = k * pow_mod(k-1, n-1, MOD) % MOD;
37         flag = -1;
38         //計算容斥 
39         for(int i = 1; i <= k-1; i ++){
40             ans = (ans + 1ll * flag * comb(k, k-i) * (k-i) % MOD * pow_mod((k-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

 

 

 

 

 

 

 

 

 

第三例題:(容斥這章的例題我可能會寫不少(o^∇^o)ノ預祝玩的開心have fun)

 

hdu 4135

http://acm.hdu.edu.cn/showproblem.php?pid=4135

 

題意:就是讓你求(a,b)區間與n互質的數的個數.

咱們能夠先求(1~b)區間的答案,而後減去(1~a-1)區間的答案

這樣問題就轉換爲(1~m)區間與n互質的數的個數

互質的很差求,咱們能夠求不互質的個數,而後減一下

 

全部咱們先求出n的全部質因數,而後用容斥作

 

AC代碼:

 1 #include<cstdio>
 2 #include<vector>
 3 using namespace std;
 4 typedef long long LL;
 5 vector <LL > prime_factor;
 6 vector <LL > vec;
 7 void init(LL x){
 8     //預處理質因子 
 9     prime_factor.clear();
10     for(LL i = 2; i*i <= x; i++){
11         if(x % i == 0){
12             prime_factor.push_back(i);
13             while(x % i == 0) x /= i;
14         }
15     }
16     if(x > 1) prime_factor.push_back(x);
17     //預處理容斥中的倍數項,符號正好是一個減一個加    
18     int vec_size;
19     vec.clear();
20     for(int i = 0; i < prime_factor.size(); i ++){
21         vec_size = vec.size();//由於vec.size()在接下來的運算中會改變 
22         for(int j = 0; j < vec_size; j ++){
23             vec.push_back(vec[j] * prime_factor[i]);
24         }
25         vec.push_back(prime_factor[i]);
26     }
27 }
28 LL work(LL x){
29     //接下來容斥
30     LL ans = x, flag = -1;
31     for(int i = 0; i < vec.size(); i ++){
32         ans += flag * x / vec[i];
33         flag = -flag;
34     }
35     return ans;
36 }
37 int main(){
38     int T;    
39     LL l, r, n;
40     scanf("%d", &T);
41     for(int cas = 1; cas <= T; cas ++){
42         scanf("%I64d%I64d%I64d", &l, &r, &n);
43         init(n); 
44         printf("Case #%d: %I64d\n", cas, work(r) - work(l-1));
45     }
46 }
View Code

 

容斥中的那些倍數我是這麼處理的

好比30 = 2 * 3 * 5

一開始數組裏面什麼都沒有

而後變成

2

而後把3挨個乘過去的值放在數組後面,同時將本身也放進數組

2 6 3

而後5也是同樣

2 6 3 10 30 15 5

最後答案n就是等於

n - n / 2 + n / 6 - n / 3 + n / 10 - n / 30 + n / 15 - n / 5

 

 

 

 

 

固然,除了數組形式,還能夠用位運算來實現容斥

AC代碼:

 1 #include<cstdio>
 2 #include<vector>
 3 using namespace std;
 4 typedef long long LL;
 5 vector <LL > prime_factor;
 6 void init(LL x){
 7     //預處理質因子 
 8     prime_factor.clear();
 9     for(LL i = 2; i*i <= x; i++){
10         if(x % i == 0){
11             prime_factor.push_back(i);
12             while(x % i == 0) x /= i;
13         }
14     }
15     if(x > 1) prime_factor.push_back(x);
16 }
17 LL work(LL x){
18     //接下來容斥
19     LL ans = x, cnt, temp;
20     for(int i = 1; i < (1 << prime_factor.size()); i ++){
21         cnt = 0;
22         temp = 1;
23         for(int j = 0; j < prime_factor.size(); j ++){
24             if(i & (1 << j)){
25                 temp *= prime_factor[j];
26                 cnt ++;
27             }
28         }
29         if(cnt & 1) ans -= x / temp;
30         else ans += x / temp;
31     }
32     return ans;
33 }
34 int main(){
35     int T;    
36     LL l, r, n;
37     scanf("%d", &T);
38     for(int cas = 1; cas <= T; cas ++){
39         scanf("%I64d%I64d%I64d", &l, &r, &n);
40         init(n); 
41         printf("Case #%d: %I64d\n", cas, work(r) - work(l-1));
42     }
43 }
View Code

 

 

 

 

 

 

 

 

 

 

 

 

第四例題:

hdu 1695

http://acm.hdu.edu.cn/showproblem.php?pid=1695

 

題意:給你5個數a,b,c,d,k

在a~b中選一個x, c~d中選一個y,知足gcd(x,y) = k , 求(x,y) 的對數 

a, b, c, d, k, 0 < a <= b <= 100,000, 0 < c <= d <= 100,000, 0 <= k <= 100,000

在題目描述的最後一行有一句話,多組裏面全部的a和c都是1(這題目不是坑爹嗎(╯‵□′)╯︵┻━┻那輸入a和c有什麼用)

而後題目變成

在1~b中選一個x, 1~d中選一個y,知足gcd(x,y) = k , 求(x,y) 的對數 。。。(無語中。。。)

 

 

 

 

 

 

gcd(x, y) == k 說明x,y都能被k整除, 可是能被k整除的未必gcd=k  , 必須還要知足互質關係

那麼問題就轉化爲

求1~b/k 和 1~d/k間,gcd(x,y) = 1對數的問題

假設b/k小於d/k

那麼當y <= b/k時,就是求1到b/k的歐拉函數的和

y > b/k時,只好枚舉y從b/k到d/k,用第3例題的求法

這樣問題就解決了(注意:k能夠等於0,要特判)

 

AC代碼:

 1 #include<cstdio>
 2 #include<algorithm>
 3 #include<vector>
 4 using namespace std;
 5 typedef long long LL;
 6 const int N = 1e5+10 ;
 7 vector <LL > prime_factor;
 8 int phi[N], prime[N];
 9 int tot;//tot計數,表示prime[N]中有多少質數 
10 void Euler(){
11     phi[1] = 1;
12     for(int i = 2; i < N; i ++){
13         if(!phi[i]){
14             phi[i] = i-1;
15             prime[tot ++] = i;
16         }
17         for(int j = 0; j < tot && 1ll*i*prime[j] < N; j ++){
18             if(i % prime[j]) phi[i * prime[j]] = phi[i] * (prime[j]-1);
19             else{
20                 phi[i * prime[j] ] = phi[i] * prime[j];
21                 break;
22             }
23         }
24     }
25 }
26 void getFactors(int x){
27     prime_factor.clear();
28     for(int i = 0; prime[i] <= x / prime[i]; i ++){
29         if(x % prime[i] == 0){
30             prime_factor.push_back(prime[i]);
31             while(x % prime[i] == 0) x /= prime[i];
32         }
33     }
34     if(x > 1) prime_factor.push_back(x);
35 }
36 LL work(int n, int m){
37     LL ans = n, cnt, temp;
38     getFactors(m);
39     for(int i = 1; i < (1 << prime_factor.size()); i ++){
40         cnt = 0;
41         temp = 1;
42         for(int j = 0; j < prime_factor.size(); j ++){
43             if(i & (1 << j)){
44                 temp *= prime_factor[j];
45                 cnt ++;
46             }
47         }
48         if(cnt & 1) ans -= n / temp;
49         else ans += n / temp;
50     }
51     return ans;
52 }
53 int main(){
54     Euler();
55     int T, a, b, c, d, k;
56     LL ans;
57     scanf("%d", &T);
58     for(int cas = 1; cas <= T; cas ++){
59         scanf("%d%d%d%d%d", &a, &b, &c, &d, &k);
60         if(k == 0){
61             printf("Case %d: 0\n", cas);
62             continue;
63         }
64         if(b > d) swap(b, d);//假設b<=d
65         b /= k; d /= k; 
66         ans = 0;
67         for(int i = 1; i <= b; i ++) ans += phi[i];
68         for(int i = b + 1; i <= d; i ++) ans += work(b, i);
69         printf("Case %d: %I64d\n", cas, ans);
70     }
71 }
View Code

 

 

 

這題時間只能算卡過去的,由於正常計算下來,這樣的代碼會超時,只是數據水

這題正確的作法應該是莫比烏斯反演,咱們之後會講到

 

 

 

 

 

 

 

 

 

容來容去,腦子都亂了。。。。

 >﹏<

相關文章
相關標籤/搜索