容斥原理初探
技術標籤:數學 - 容斥原理
整理的演算法模板合集: ACM模板
實際上是一個全新的精煉模板整合計劃
以下內容摘自 我的文章:演算法競賽中的數論問題 - 數論全家桶(信奧 / 數競 / ACM)作者孟繁宇,四萬字,十三萬字元的競賽數論完全總結,將會擇機發布,敬請期待 ~
0x15 容斥原理初探
容斥原理是一種應用在集合上的較常用的計數方法,其基本思想是:先不考慮重疊的情況,把包含於某內容中的所有物件的數目先計算出來(容),然後再把計數時重複計算的數目排斥出去(斥),使得計算的結果既無遺漏又無重複。
容斥原理核心的計數規則可以歸為一句話:奇加偶減。
假設被計數的有
A
、
B
、
C
A、B、C
即:
A
∪
B
∪
C
=
A
+
B
+
C
−
A
B
−
B
C
−
A
C
+
A
B
C
A∪B∪C = A+B+C - AB - BC - AC + ABC
當被計數的種類被推到 n n n 類時,其統計規則遵循奇加偶減。
- 競賽例題選講
Problem A Co-prime(HDU 4135)
求 [ a , b ] [a,b] [a,b] 區間與 n n n 互質的數的個數,其中 1 ≤ a , b , n ≤ 2 31 1\le a,b,n\le2^{31} 1≤a,b,n≤231。
Solution
容斥定理最常用於求解
[
a
,
b
]
[a,b]
[a,b] 區間與
n
n
n 互質的數的個數問題,該問題可以視為求
[
1
,
b
]
[1,b]
[1,b] 區間與
n
n
那麼我們這裡只需要考慮它的一個子問題:
求小於等於 m m m 且 n n n 互素的數的個數。
我們知道當 m m m 等於 n n n ,就是一個簡單的尤拉函式問題,但是一般 m m m 都不等於 n n n ,我們考慮將 n n n 質因數分解。
我們首先分析最簡單的情況:當 n n n 為素數的冪,即 n = p k n = p^k n=pk 時,那麼顯然答案就等於 m − m p m - \cfrac{m}{p} m−pm (其中 m p \cfrac{m}{p} pm 表示的是 p p p 的倍數, 1 1 1 ~ m m m 中去掉 p p p 的倍數,剩下的就都是與 n n n 互素的數了)
然後再來討論 n n n 是兩個素數的冪的乘積,即 n = p 1 k 1 × p 2 k 2 n = p_1^{k_1} \times p_2^{k_2} n=p1k1×p2k2,那麼我們需要做的就是找到 p 1 p_1 p1 的倍數和 p 2 p_2 p2 的倍數,並且要減去 p 1 p_1 p1 和 p 2 p_2 p2 的公倍數,我們發現這實際上就是容斥原理,所以這種情況下答案為: m − ( m p 1 + m p 2 − m p 1 × p 2 ) m - ( \cfrac{m}{p_1} + \cfrac{m}{p_2} - \cfrac{m}{p_1\times p_2} ) m−(p1m+p2m−p1×p2m)。
這裡的 + + + 就是 容, − - − 就是 斥,並且 容 和 斥 總是交替進行的(一個的加上,兩個的減去,三個的加上,四個的減去),而且可以推廣到 n n n 個元素的情況,如果 n n n 分解成 s s s 個素因子,也同樣可以用容斥原理求解。
容斥原理其實是列舉子集的過程,常見的列舉方法為 d f s dfs dfs ,也可以採用二進位制法( 0 0 0 表示取, 1 1 1 表示不取)。
例如我們求 [ 1 , 9 ] [1, 9] [1,9] 中和 6 6 6 互素的數的個數,這時 6 6 6 分解的素因子為 2 2 2 和 3 3 3 。
a n s = 9 − ( 9 2 + 9 3 ) + 9 6 = 3 ans = 9 - (\cfrac{9}{2} + \cfrac{9}{3}) + \cfrac{9}{6} = 3 ans=9−(29+39)+69=3,其中, a n s ans ans 分為三部分, 0 0 0 個數的組合, 1 1 1 個數的組合, 2 2 2 個數的組合。
答案 = ( 1 1 1 ~ b b b 的元素個數) − - − ( 1 1 1 ~ a − 1 a-1 a−1 的元素個數) − - − ( 1 1 1 ~ b b b 中與 n n n 不互質的數的個數) + + + ( 1 1 1 ~ a − 1 a-1 a−1 中與 n n n 互質的數的個數)
Code
const int N = 10005;
ll n, primes[N], cnt, factor[N], num;
bool vis[N];
inline void get_primes(int n)
{
for(register int i = 2;i <= n;i ++) {
if(!vis[i]) primes[ ++ cnt] = i;
for(register int j = 1;j <= cnt && i * primes[j] <= n; ++ j) {
vis[i * primes[j]] = 1;
if(i % primes[j] == 0) break;
}
}
}
void get_factor(int n) {
num = 0;
for (ll i = 1; primes[i] * primes[i] <= n && i <= cnt; i ++ ) {
if (n % primes[i] == 0) { //記錄n的因子
factor[num ++ ] = primes[i];
while (n % primes[i] == 0)
n /= primes[i];
}
}
if (n != 1) //1既不是素數也不是合數
factor[num ++ ] = n;
}
ll solve(ll m, ll num) {
ll res = 0;
for (ll i = 1; i < (1 << num); i++) {
ll sum = 0;
ll temp = 1;
for (ll j = 0; j < num; j++) {
if (i & (1 << j)) {
sum ++ ;
temp *= factor[j];
}
}
if (sum % 2) res += m / temp;
else res -= m / temp;
}
return res;
}
int kcase, t;
int main() {
get_primes(N - 1);
scanf("%d", &t);
while(t -- ) {
ll a, b, n;
scanf("%lld%lld%lld", &a, &b, &n);
get_factor(n);
//容斥定理,奇加偶減,
ll res = (b - (a - 1) - solve(b, num)) + solve(a - 1, num);
printf("Case #%d: %lld\n", ++ kcase, res);
}
return 0;
}
Problem B Helping Cicada(LightOJ - 1117)
求 [ 1 , n ] [1,n] [1,n] 中不能被 m m m 個數整除的個數。
Solution
我們知道對於任意一個數 k k k ,在 [ 1 , n ] [1,n] [1,n] 中有 ⌊ n k ⌋ \lfloor\cfrac{n}{k}\rfloor ⌊kn⌋ 個數是 k k k 的倍數,也就是能被 k k k 整除,故 a n s = n − ∑ i = 1 n ⌊ n a [ i ] ⌋ ans=n-\sum_{i=1}^n\lfloor\cfrac{n}{a[i]}\rfloor ans=n−∑i=1n⌊a[i]n⌋ 。
我們再來考慮兩個數 a , b a,b a,b 的情況,因為 a , b a,b a,b 的公倍數 l c m ( a , b ) lcm(a,b) lcm(a,b),即被 a a a 整除,又被 b b b 整除,所以減了兩次。所以最後的答案要加上 ⌊ n l c m ( a , b ) ⌋ \lfloor\cfrac{n}{lcm(a,b)}\rfloor ⌊lcm(a,b)n⌋ 。對於三個數 a , b , c a,b,c a,b,c 來說,答案就要減去 ⌊ n l c m ( a , b , c ) ⌋ \lfloor\cfrac{n}{lcm(a,b,c)}\rfloor ⌊lcm(a,b,c)n⌋ ,以此類推
最後拓展到 m m m 個數的情況,我們發現需要使用容斥定理。
根據容斥定理的奇加偶減,對於 m m m 個數來說,其中的任意 2 、 4 、 ⋯ 、 2 k 2、4、\cdots、2k 2、4、⋯、2k 個數就要減去他們最小公倍數能組成的數, 1 、 3 、 ⋯ 、 2 k + 1 1、3、\cdots、2k+1 1、3、⋯、2k+1 個數就要加上他們的最小公倍數,對於每一個數來說,我們都有選或不選兩種情況,因此 m m m 個數就有 2 m 2^m 2m 種情況,也就是從 0 0 0(啥也不選)到 2 m − 1 2^m-1 2m−1,對應二進位制就是 m m m 個 1 1 1,也就意味著 我們選擇了 m m m 個數。這種方法叫做狀態壓縮,我們接用狀態壓縮來列舉所有的可能狀態,依次使用位運算判斷當前的狀態選擇了多少個數,然後再進行奇加偶減即可。
s u m sum sum = = = (從 m m m 中選 1 1 1 個數得到的倍數的個數) − - − (從 m m m 中選 2 2 2 個數得到的倍數的個數) + + + (從 m m m 中選 3 3 3 個數得到的倍數的個數) − - − (從 m m m 中選 4 4 4 個數得到的倍數的個數)……
那麼能被整除的數的個數就是 s u m sum sum,不能被整除的數的個數就是 n − s u m n-sum n−sum 。
Code
const int N = 10005;
ll LCM(ll a, ll b) {
return a / __gcd(a, b) *b;
}
int m, kcase;
ll n, a[N];
int main()
{
int t;
scanf("%d", &t);
while(t -- ) {
scanf("%lld%d", &n, &m);
for(int i = 0; i < m; ++ i)
scanf("%lld", &a[i]);
ll sum = 0;
for(int i = 0; i < (1 << m); ++ i) {
ll lcm = 1;
ll cnt = 0;
for(int j = 0; j < m; ++ j) {
if(i >> j & 1) {//如當前的狀態i選擇了第j個數
lcm = LCM(lcm, a[j]);//那就選第j個數
cnt ++ ;
}
}
if(cnt != 0) {
if(cnt & 1) sum += n / lcm;//奇加
else sum -= n / lcm;//偶減
}
}
printf("Case %d: %lld\n", ++ kcase, n - sum);
}
return 0;
}