容斥原理
说的是一种计数方式,使得每种情况只被记一次。
∑ i = 1 n C n i ( − 1 ) i + 1 = [ n ≥ 1 ] \sum_{i=1}^{n}C_n^i(-1)^{i+1}=[n\geq 1] ∑i=1nCni(−1)i+1=[n≥1]
入门例题
(1) HDU 1465 \text{(1) HDU 1465} (1) HDU 1465
错排问题模板题。
推一下递推式子:
把 n n n个 i ̸ = a i i\not = a_i i̸=ai当成 n n n个条件然后容斥,每次选一些不错位,剩下随便排.
d p [ n ] = ∑ i = 0 n ( − 1 ) i + 1 C n i ( n − i ) ! dp[n]=\sum_{i=0}^n (-1)^{i+1}C_{n}^{i}(n-i)! dp[n]=∑i=0n(−1)i+1Cni(n−i)!
d p [ n ] = ∑ i = 0 n ( − 1 ) i + 1 n ! i ! ( n − i ) ! ( n − i ) ! dp[n]=\sum_{i=0}^n (-1)^{i+1}\frac{n!}{i!(n-i)!}(n-i)! dp[n]=∑i=0n(−1)i+1i!(n−i)!n!(n−i)!
d p [ n ] = ∑ i = 0 n ( − 1 ) i + 1 n ! i ! dp[n]=\sum_{i=0}^n (-1)^{i+1}\frac{n!}{i!} dp[n]=∑i=0n(−1)i+1i!n!
d p [ n ] = ( − 1 ) n + ∑ i = 0 n − 1 ( − 1 ) i + 1 n ! i ! dp[n]=(-1)^n+\sum_{i=0}^{n-1} (-1)^{i+1}\frac{n!}{i!} dp[n]=(−1)n+∑i=0n−1(−1)i+1i!n!
d p [ n ] = ( − 1 ) n + n ∑ i = 0 n − 1 ( − 1 ) i + 1 ( n − 1 ) ! i ! dp[n]=(-1)^n+n\sum_{i=0}^{n-1} (-1)^{i+1}\frac{(n-1)!}{i!} dp[n]=(−1)n+n∑i=0n−1(−1)i+1i!(n−1)!
d p [ n ] = ( − 1 ) n + n    d p [ n − 1 ] dp[n]=(-1)^n+n \;dp[n-1] dp[n]=(−1)n+ndp[n−1]
#include <cstdio>
long long dp[21];
int main() {
dp[0] = dp[1] = 0;
for(int i = 2; i <= 20; i ++)
dp[i] = i * 1ll * dp[i - 1] + ((i & 1) ? -1 : 1);
for(int n; ~ scanf("%d", &n); )
printf("%lld\n", dp[n]);
return 0;
}
(2) HDU 1796 \text{(2) HDU 1796} (2) HDU 1796
题意:给定 m ( m ≤ 10 ) m(m\leq 10) m(m≤10)个数 a i a_i ai,求小于 n n n的数中有多个数满足能被任意一个 a i a_i ai整除.
容斥应用入门题。 DFS \text{DFS} DFS容斥一下就好。即加上只被某1个整除的,减去被某2个整除的,加上…
#include <cstdio>
typedef long long LL;
int n, m, a[10];
LL ans;
LL gcd(const LL & a, const LL & b) {
return b == 0 ? a : gcd(b, a % b);
}
LL lcm(const LL & a, const LL & b) {
return a / gcd(a, b) * b;
}
void dfs(int k, int c, LL ret) {
if(k == m) {
if(c > 0) ans += ((n - 1) / ret) * ((c & 1) ? 1 : -1);
return ;
}
dfs(k + 1, c, ret);
if(a[k] > 0 && a[k] < n)
dfs(k + 1, c + 1, lcm(ret, a[k]));
}
int main() {
while(~ scanf("%d%d", &n, &m)) {
for(int i = 0; i < m; i ++)
scanf("%d", &a[i]);
ans = 0;
dfs(0, 0, 1);
printf("%lld\n", ans);
}
return 0;
}
(3) HDU 4135 \text{(3) HDU 4135} (3) HDU 4135
题意:求 [ a , b ] [a,b] [a,b]中与 n n n互质的数, a , b ≤ 1 0 15 , n ≤ 1 0 9 a,b \leq 10^{15},n\leq 10^9 a,b≤1015,n≤109.
首先拆成前缀,转换为求 [ 1 , r ] [1,r] [1,r]中与 n n n互质的数。
这个不好求,补集转化,求为求 [ 1 , r ] [1,r] [1,r]中与 n n n不互质的数,然后用 r r r减去这个答案。
不互质意味着包含 n n n的某个质因子,把 n n n质因数分解为 ∏ i = 1 k p i c i \prod_{i=1}^{k} p_i^{c_i} ∏i=1kpici。考虑一个质因子 p i p_i pi, [ 1 , r ] [1,r] [1,r]中与 p i p_i pi不互质的数个数为 ⌊ r p i ⌋ \lfloor\frac{r}{p_i}\rfloor ⌊pir⌋。这个会有重复,需要容斥。
举个例子, n = p 1 c 1 p 2 c 2 p 3 c 3 n=p_1^{c_1}p_2^{c_2}p_3^{c_3} n=p1c1p2c2p3c3,答案即为 r p 1 + r p 2 + r p 3 − r p 1 p 2 − r p 1 p 3 − r p 2 p 3 + r p 1 p 2 p 3 \frac{r}{p_1}+\frac{r}{p_2}+\frac{r}{p_3}-\frac{r}{p_1p_2}-\frac{r}{p_1p_3}-\frac{r}{p_2p_3}+\frac{r}{p_1p_2p_3} p1r+p2r+p3r−p1p2r−p1p3r−p2p3r+p1p2p3r。
#include <algorithm>
#include <cstdio>
using namespace std;
typedef long long LL;
const int N = 2e6 + 10;
LL p[N], r, ans;
int tot;
void dfs(int k, int s, LL x) {
if(k == tot + 1) {
if(x ^ 1)
ans += ((s & 1) ? 1 : -1) * (r / x);
return ;
}
dfs(k + 1, s, x);
dfs(k + 1, s + 1, x * p[k]);
}
LL solve(LL r, int n) {
if((:: r = r) < 1) return 0;
tot = 0;
int s = sqrt(n) + 0.5;
for(int i = 2; i <= s; i ++) {
if(n % i == 0) {
p[++ tot] = i;
while(n % i == 0) n /= i;
}
}
if(n > 1) p[++ tot] = n;
ans = 0;
dfs(1, 0, 1);
return r - ans;
}
int main() {
int T; scanf("%d", &T);
for(int Case = 1, n; Case <= T; ++ Case) {
LL a, b;
scanf("%lld%lld%d", &a, &b, &n);
printf("Case #%d: %lld\n", Case, solve(b, n) - solve(a - 1, n));
}
return 0;
}
容斥的实现可以 DFS \text{DFS} DFS也可以状压,以这题为例,演示一下状压写法。
#include <algorithm>
#include <cstdio>
using namespace std;
typedef long long LL;
const int N = 2e6 + 10;
LL p[N], r, ans;
int tot;
void calc() {
ans = 0;
for(int i = 1; i < 1 << tot; i ++) {
int cnt = 0;
LL prod = 1;
for(int j = 0; j < tot; j ++)
if(i >> j & 1) ++ cnt, prod *= p[j];
ans += ((cnt & 1) ? 1 : -1) * (r / prod);
}
}
LL solve(LL r, int n) {
if((:: r = r) < 1) return 0;
tot = 0;
int s = sqrt(n) + 0.5;
for(int i = 2; i <= s; i ++) {
if(n % i == 0) {
p[tot ++] = i;
while(n % i == 0) n /= i;
}
}
if(n > 1) p[tot ++] = n;
calc();
return r - ans;
}
int main() {
int T; scanf("%d", &T);
for(int Case = 1, n; Case <= T; ++ Case) {
LL a, b;
scanf("%lld%lld%d", &a, &b, &n);
printf("Case #%d: %lld\n", Case, solve(b, n) - solve(a - 1, n));
}
return 0;
}
(4) HDU 2204 \text{(4) HDU 2204} (4) HDU 2204
题意:求 [ 1 , n ] [1,n] [1,n]中能表示成 m k ( k > 1 ) m^k(k>1) mk(k>1)的数的个数, n ≤ 1 0 18 n\leq 10^{18} n≤1018。
首先 m = 1 m=1 m=1单独算, 1 1 1个。考虑 m ≥ 2 m\geq2 m≥2;
2 63 > 1 0 18 2^{63}>10^{18} 263>1018,因此 k ≤ 63 k\leq 63 k≤63。
我们只需要考虑 k k k是质数的情况,因为 k k k不是质数的时候一定会在 k k k是质数的时候考虑到。
预处理出 63 63 63以内的质数:
{ 2 , 3 , 5 , 7 , 11 , 13 , 17 , 19 , 23 , 29 , 31 , 37 , 41 , 43 , 47 , 53 , 59 } \{2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59\} {2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59}
考虑某个质数 p i p_i pi, n n n以内能表示成 m p i m^{p_i} mpi的数有 ⌊ n p i ⌋ \lfloor\sqrt[p_i]{n}\rfloor ⌊pin⌋个(即 ⌊ n 1 p i ⌋ \lfloor n^{\frac{1}{p_i}}\rfloor ⌊npi1⌋个)
每个质数的答案贡献的集合有可能相交,因此需要容斥
20 20 20个数做 2 20 2^{20} 220的容斥?
注意到其实 4 4 4个质数就已经没有交集了, 2 × 3 × 5 × 7 = 210 > 63 2\times 3 \times 5 \times 7=210>63 2×3×5×7=210>63。
因此只需三重循环即可。
#include <algorithm>
#include <cstdio>
using namespace std;
typedef long long LL;
int p[20] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59};
int main() {
for(LL n; ~ scanf("%lld", &n); ) {
LL ans = 0, tmp;
for(int i = 0; i < 20; i ++) {
tmp = (LL) pow(n, 1.0 / p[i]);
if(tmp < 1) break ;
ans += tmp - 1;
for(int j = i + 1; j < 20; j ++) {
tmp = (LL) pow(n, 1.0 / (p[i] * p[j]));
if(tmp < 1) break ;
ans -= tmp - 1;
for(int k = j + 1; k < 20; k ++) {
tmp = (LL) pow(n, 1.0 / (p[i] * p[j] * p[k]));
if(tmp < 1) continue ;
ans += tmp - 1;
}
}
}
printf("%lld\n", ans + 1);
}
return 0;
}
推系数的容斥例题
(1) CDOJ 1544 \text{(1) CDOJ 1544} (1) CDOJ 1544
CDOJ 题目传送门 , Ifrog 备用传送门 , Vjudge 备用传送门 , UESTC 备用传送门
这是“玲珑杯”线上赛 Round \text{Round} Round # 17 17 17 河南专场 B \text{B} B 题。
题意:有
n
n
n个硬币,初始全部正面朝上,现在有
m
m
m次操作,每次把编号是
x
x
x的倍数的硬币翻面,
最后问多少个硬币正面朝上。
1
≤
N
≤
1
0
9
,
1
≤
M
≤
15
,
1
≤
x
≤
2
×
1
0
5
1\leq N \leq 10^9,1\leq M\leq 15,1\leq x\leq 2\times 10^5
1≤N≤109,1≤M≤15,1≤x≤2×105
原问题转化为:给定 m m m个数 a 1 , . . . , a m a_1,...,a_m a1,...,am, 统计 [ 1 , n ] [1,n] [1,n]的整数中, 满足 a 1 , . . . , a m a_1,...,a_m a1,...,am 中有奇数个数整除它的数的个数。
即:
其他容斥题
(1) HDU 4336 \text{(1) HDU 4336} (1) HDU 4336
题意:有 n n n个卡牌,一包零食中有第 i i i张卡牌的概率为 p i p_i pi,求期望买多少包能收集到所有卡牌。
考虑单独一张卡牌 i i i,期望 E { i } = 1 p i E_{\{i\}}=\frac{1}{p_i} E{i}=pi1,这个比较好理解。
考虑两张卡牌 i , j i,j i,j,期望 E { i , j } = 1 p i + 1 p j − 1 p i + p j E_{\{i,j\}}=\frac{1}{p_i}+\frac{1}{p_j}-\frac{1}{p_i+p_j} E{i,j}=pi1+pj1−pi+pj1。即收集 i , j i,j i,j的期望为收集 i i i的期望加收集 j j j的期望,但是存在情况就是考虑 i i i的时候虽然没收集到 i i i但收集到了 j j j,考虑 j j j的时候虽然没收集到 j j j但收集到了 i i i。
三张以及以上直接容斥即可。
#include <cstdio>
int main() {
double * p = new double [21];
for(int n; ~ scanf("%d", &n); ) {
for(int i = 0; i < n; i ++)
scanf("%lf", &p[i]);
double ans = 0;
for(int s = 1; s < 1 << n; ++ s) {
int cnt = -1; double a = 0;
for(int i = 0; i < n; i ++) {
if(s >> i & 1) {
cnt *= -1;
a += p[i];
}
}
ans += cnt / a;
}
printf("%.4f\n", ans);
}
return 0;
}