容斥原理
1. 容斥原理
原理
- 容斥原理属于集合论的内容。
- 如果使用
|S|
表示集合S中元素的个数,那么高中我们最常见的是三个集合的情形:给定三个集合 S 1 、 S 2 、 S 3 S_1、S_2、S_3 S1、S2、S3,求这三个集合元素的个数(重复的元素技能计算一次),如下图:
∣ S 1 ∪ S 2 ∪ S 3 ∣ = ∣ S 1 ∣ + ∣ S 2 ∣ + ∣ S 3 ∣ − ∣ S 1 ∩ S 2 ∣ − ∣ S 1 ∩ S 3 ∣ − ∣ S 2 ∩ S 3 ∣ + ∣ S 1 ∩ S 2 ∩ S 3 ∣ |S_1 \cup S_2 \cup S_3| = |S_1| + |S_2| + |S_3| - |S_1 \cap S_2| - |S_1 \cap S_3|- |S_2 \cap S_3| + |S_1 \cap S_2 \cap S_3| ∣S1∪S2∪S3∣=∣S1∣+∣S2∣+∣S3∣−∣S1∩S2∣−∣S1∩S3∣−∣S2∩S3∣+∣S1∩S2∩S3∣
- 推广:对于n个集合,让求解一共出现了多少个元素,则有如下公式:
∣ ⋃ i S i ∣ = ∑ i ∣ S i ∣ − ∑ i , j ∣ S i ∩ S j ∣ + ∑ i , j , k ∣ S i ∩ S j ∩ S k ∣ − . . . . . . \Big\lvert \bigcup _i S_i \Big\rvert = \sum _ i |S_i| - \sum _{i,j} |S_i \cap S_j| + \sum _{i,j,k} |S_i \cap S_j \cap S_k| - ...... ∣∣∣i⋃Si∣∣∣=i∑∣Si∣−i,j∑∣Si∩Sj∣+i,j,k∑∣Si∩Sj∩Sk∣−......
上式就是容斥原理内容。
-
如下是对容斥原理的证明:思路是查看每个元素是否是只被计算了一次,如果是的话,说明公式是正确的。
不妨设元素
x
在k
个集合( 1 ≤ k ≤ n 1 \le k \le n 1≤k≤n)中出现过,根据公式则其被计算的次数为:
C k 1 − C k 2 + C k 3 − . . . + ( − 1 ) k − 1 C k k C_k^1 - C_k^2 + C_k^3 - ... + (-1)^{k-1}C_k^k Ck1−Ck2+Ck3−...+(−1)k−1Ckk
根据二项式定理,可知:
C k 1 − C k 2 + C k 3 − . . . + ( − 1 ) k − 1 C k k = − ( 1 − 1 ) k + C k 0 = 1 C_k^1 - C_k^2 + C_k^3 - ... + (-1)^{k-1}C_k^k = -(1 - 1) ^ k + C_k^0 = 1 Ck1−Ck2+Ck3−...+(−1)k−1Ckk=−(1−1)k+Ck0=1
因此每个元素只计算了一次,该公式是正确的。 -
另外我们考虑一个容斥原理中等式右侧有多少项相加减,第一项相当于从n个元素中拿1个,第二项相当于从n个元素中拿2个,…,因此相加减的项数有:
C n 1 + C n 2 + C n 3 − . . . + C n n C_n^1 + C_n^2 + C_n^3 - ... + C_n^n Cn1+Cn2+Cn3−...+Cnn
那么这个式子等于多少呢?如果上面再加上一项 C n 0 C_n^0 Cn0,表示从n个元素中拿0个,则相当于问n个元素有多少个子集,考虑每个元素都可选可不选,因此一共有 2 n 2^n 2n个子集,因此有:
C n 0 + C n 1 + C n 2 + C n 3 − . . . + C n n = 2 n C_n^0 + C_n^1 + C_n^2 + C_n^3 - ... + C_n^n = 2^n Cn0+Cn1+Cn2+Cn3−...+Cnn=2n
整理可以得到:
C n 1 + C n 2 + C n 3 − . . . + C n n = 2 n − 1 C_n^1 + C_n^2 + C_n^3 - ... + C_n^n = 2^n - 1 Cn1+Cn2+Cn3−...+Cnn=2n−1
一共有 2 n − 1 2^n-1 2n−1项相加减。
2. AcWing上的容斥原理题目
AcWing 890. 能被整除的数
问题描述
-
问题链接:AcWing 890. 能被整除的数
分析
-
最简单的思路是对于每个整数
x
,判断这m个质数中是否存在能整数x
,如果存在,答案加一,这样做法的时间复杂度是 O ( n × m ) O(n \times m) O(n×m)的,会TLE
,不可取。 -
另一种思路是使用容斥原理,考察能被 p i ( 1 ≤ i ≤ m ) p_i(1 \le i \le m) pi(1≤i≤m),能被 p i p_i pi 整除的数的数量为 ⌊ n p i ⌋ \lfloor \frac{n}{p_i} \rfloor ⌊pin⌋,结果加上这些数量,但是会有重复,比如同时能被两个质数 p i , p j ( 1 ≤ i , j ≤ m ) p_i, p_j(1 \le i, j \le m) pi,pj(1≤i,j≤m) 整除的数据被计算了两次,需要减去,…,因此最终的答案:
∑ i ⌊ n p i ⌋ − ∑ i , j ⌊ n p i × p j ⌋ + ∑ i , j , k ⌊ n p i × p j × p k ⌋ − . . . . . . \sum _ i \lfloor \frac{n}{p_i} \rfloor - \sum _ {i, j} \lfloor \frac{n}{p_i \times p_j} \rfloor + \sum _ {i, j, k} \lfloor \frac{n}{p_i \times p_j \times p_k} \rfloor -...... i∑⌊pin⌋−i,j∑⌊pi×pjn⌋+i,j,k∑⌊pi×pj×pkn⌋−......
-
时间复杂度:一共有 2 m − 1 2^m-1 2m−1项相加减,每次计算主要是质数相乘,最多相乘
m-1
次,因此时间复杂度为 O ( 2 m × m ) O(2^m \times m) O(2m×m)。 -
另外还要考虑如何遍历所有的可能组合,这有一个常用的技巧,我们可以通过二进制的方式进行遍历,即从1循环到 2 m − 1 2^m-1 2m−1,对于其中的每个数据,对应一种集合,该数据的二进制表示中如果某一位是1,代表需要乘上该质数。
代码
- C++
#include <iostream>
using namespace std;
typedef long long LL;
const int N = 20;
int p[N]; // 输入的质数
int main() {
int n, m;
cin >> n >> m;
for (int i = 0; i < m; i++) cin >> p[i];
int res = 0;
for (int i = 1; i < 1 << m; i++) {
int t = 1, s = 0; // t表示当前i对应集合中所有质数乘积, s表示集合中数据个数
for (int j = 0; j < m; j++)
if (i >> j & 1) {
if ((LL)t * p[j] > n) {
t = -1; // 乘积大于n,res不用变
break;
}
t *= p[j];
s++;
}
if (t != -1) {
if (s % 2) res += n / t;
else res -= n / t;
}
}
cout << res << endl;
return 0;
}
AcWing 214. Devu和鲜花
问题描述
-
问题链接:AcWing 214. Devu和鲜花
分析
-
首先我们考虑一个简化版的该问题,假设每个盒子中的花有无限多个,我们有多少中种选择方案?
-
假设每个盒子中选择的花的数量为 x i x_i xi,则 x 1 + x 2 + . . . + x N = M , 其 中 x i ≥ 0 x_1 + x_2 + ...+ x_N = M,其中x_i \ge 0 x1+x2+...+xN=M,其中xi≥0,我们令 y i = x i + 1 y_i = x_i + 1 yi=xi+1,则 y 1 + y 2 + . . . + y N = M + N , 其 中 y i > 0 y_1 + y_2 + ...+ y_N = M + N,其中y_i > 0 y1+y2+...+yN=M+N,其中yi>0,我们可以使用隔板法解决该问题,类似于组合计数中的AcWing 1312. 序列统计,不过本题是等号,AcWing1312是小于等于。
-
M+N
个小球可以在M+N-1
个缝隙中放置N-1
个隔板,因此存在 C M + N − 1 N − 1 C_{M+N-1}^{N-1} CM+N−1N−1种方案。 -
然后我们考虑限制条件,这里要求 x i ≤ A i x_i \le A_i xi≤Ai,利用补集的思想,我们考虑不满足各个限制条件的情况,让 S i S_i Si表示不满足 x i ≤ A i x_i\le A_i xi≤Ai的所有方案的集合(即在这个集合中一定有 x i > A i x_i > A_i xi>Ai),例如 S 1 S_1 S1表示不满足第一个条件的集合(其他条件满不满足无所谓)。则总体的方案数(所有方案-不合法的方案):
C M + N − 1 N − 1 − ∣ S 1 ∪ S 2 ∪ . . . ∪ S N ∣ C_{M+N-1}^{N-1} - |S_1 \cup S_2 \cup ... \cup S_N| CM+N−1N−1−∣S1∪S2∪...∪SN∣
- 其中不合法的方案数可以使用容斥原理求解。考虑 ∣ S 1 ∪ S 2 ∪ . . . ∪ S N ∣ |S_1 \cup S_2 \cup ... \cup S_N| ∣S1∪S2∪...∪SN∣的求解,将该式展开得到:
∣ ⋃ i S i ∣ = ∑ i ∣ S i ∣ − ∑ i < j ∣ S i ∩ S j ∣ + ∑ i < j < k ∣ S i ∩ S j ∩ S k ∣ − . . . . . . \Big\lvert \bigcup _i S_i \Big\rvert = \sum _ i |S_i| - \sum _{i<j} |S_i \cap S_j| + \sum _{i<j<k} |S_i \cap S_j \cap S_k| - ...... ∣∣∣i⋃Si∣∣∣=i∑∣Si∣−i<j∑∣Si∩Sj∣+i<j<k∑∣Si∩Sj∩Sk∣−......
-
我们考虑 ∣ S 1 ∣ |S_1| ∣S1∣的求解,根据定义, S 1 S_1 S1对应的集合中一定有 x 1 > A 1 x_1 > A_1 x1>A1,我们可以首先让 x 1 = A 1 + 1 x_1=A_1+1 x1=A1+1,之后可以在
N
个盒子中选取 M − ( A 1 + 1 ) M-(A_1+1) M−(A1+1)只花(每个盒子中的花可以看成无限的),这就是 S 1 S_1 S1对应的所有方案;从N
个盒子中选取 M − ( A 1 + 1 ) M-(A_1+1) M−(A1+1)只花(每个盒子中的花可以看成无限的)就是我们刚开始考虑的问题,因此 S 1 S_1 S1对应的方案数为 C M − ( A 1 + 1 ) + N − 1 N − 1 C_{M-(A_1+1)+N-1}^{N-1} CM−(A1+1)+N−1N−1。 -
类似的考虑 ∣ S 1 ∩ S 2 ∣ |S_1 \cap S_2| ∣S1∩S2∣的计算,则我们需要让 x 1 = A 1 + 1 , x 2 = A 2 + 1 x_1=A_1+1,x_2=A_2+1 x1=A1+1,x2=A2+1,之后可以在
N
个盒子中选取 M − ( A 1 + 1 ) − ( A 2 + 1 ) M-(A_1+1)-(A_2+1) M−(A1+1)−(A2+1)只花(每个盒子中的花可以看成无限的),这就是 ∣ S 1 ∩ S 2 ∣ |S_1 \cap S_2| ∣S1∩S2∣对应的所有方案;因此 ∣ S 1 ∩ S 2 ∣ |S_1 \cap S_2| ∣S1∩S2∣对应的方案数为 C M − ( A 1 + 1 ) − ( A 2 + 1 ) + N − 1 N − 1 C_{M-(A_1+1)-(A_2+1)+N-1}^{N-1} CM−(A1+1)−(A2+1)+N−1N−1。 -
以此类推我们可以求出不合法的方案数量。因此我们最终的合法方案数量为:
r e s = C M + N − 1 N − 1 − ∑ i C M + N − A i − 2 N − 1 + ∑ i < j C M + N − A i − A j − 3 N − 1 − . . . . . . res = C_{M+N-1}^{N-1} - \sum _ i C_{M+N-A_i-2}^{N-1} + \sum _ {i<j} C_{M+N-A_i-A_j-3}^{N-1} - ...... res=CM+N−1N−1−i∑CM+N−Ai−2N−1+i<j∑CM+N−Ai−Aj−3N−1−......
- 考虑上式如何求解,容斥原理展开式中一共有
2
N
−
1
2^N-1
2N−1项,上式中第一项可以看成一个
S
S
S也不选的情况,一共有
2
N
2^N
2N项,可以使用二进制数据枚举所有情况,因此本题的时间复杂度为
2
N
×
N
2^N \times N
2N×N,这里乘以
N
是因为我们求解 C M + N − 1 N − 1 C_{M+N-1}^{N-1} CM+N−1N−1的计算量为 O ( N ) O(N) O(N),因为N
很小,我们按照定理求解 C M + N − 1 N − 1 C_{M+N-1}^{N-1} CM+N−1N−1即可,即:
C M + N − 1 N − 1 = ( M + N − 1 ) × ( M + N − 2 ) × . . . × ( M + 1 ) ( N − 1 ) ! C_{M+N-1}^{N-1} = \frac{(M+N-1) \times (M+N-2) \times ... \times (M+1)}{(N-1)!} CM+N−1N−1=(N−1)!(M+N−1)×(M+N−2)×...×(M+1)
- 我们使用
up、down
分别表示上式的分子和分母的逆元(可以使用快速幂求解,因为两者互质且题目给的模数为质数),我们发现分母是不会变的,因此可以只计算一次,这样本题就不会存在最后一个数据无法通过的情况了。
代码
- C++
#include <iostream>
using namespace std;
typedef long long LL;
const int N = 25, mod = 1e9 + 7;
LL n, m;
LL A[N];
int down = 1;
int qmi(int a, int k, int p) {
int res = 1 % p;
while (k) {
if (k & 1) res = (LL)res * a % p;
a = (LL)a * a % p;
k >>= 1;
}
return res;
}
int C(LL a, LL b) {
if (a < b) return 0;
int up = 1;
for (LL i = a; i > a - b; i--) up = i % mod * up % mod; // i本身就是LL
return (LL)up * down % mod;
}
int main() {
cin >> n >> m;
for (int i = 0; i < n; i++) cin >> A[i];
// 计算(n-1)!对mod的逆元
for (int i = 1; i <= n - 1; i++) down = (LL)down * i % mod;
down = qmi(down, mod - 2, mod);
int res = 0;
for (int i = 0; i < 1 << n; i++) {
LL a = m + n - 1, b = n - 1;
int sign = 1; // 选择了偶数项S系数为正,否则为负
for (int j = 0; j < n; j++)
if (i >> j & 1) {
sign *= -1;
a -= A[j] + 1;
}
res = (res + C(a, b) * sign) % mod;
}
cout << (res + mod) % mod << endl;
return 0;
}
AcWing 215. 破译密码
问题描述
-
问题链接:AcWing 215. 破译密码
分析
-
本题中的需要用到知识点:莫比乌斯函数(Mobius函数)。下面给出该函数的定义:
-
对于给定正整数
x
,如果x
可以被质因数分解为 p 1 α 1 ∗ p 2 α 2 ∗ . . . ∗ p k α k p_1^{\alpha_1}*p_2^{\alpha_2}*...*p_k^{\alpha_k} p1α1∗p2α2∗...∗pkαk,莫比乌斯函数记作 μ ( x ) \mu (x) μ(x),则:
μ ( x ) = { 0 至 少 存 在 一 个 α 的 值 大 于 1 1 所 有 α 值 都 为 1 , 且 k 为 偶 数 − 1 所 有 α 值 都 为 1 , 且 k 为 奇 数 \mu (x) = \begin{cases} 0 \quad \quad 至少存在一个\alpha的值大于1 \\ 1 \quad \quad 所有\alpha值都为1,且k为偶数 \\ -1 \quad \quad 所有\alpha值都为1,且k为奇数 \end{cases} μ(x)=⎩⎪⎨⎪⎧0至少存在一个α的值大于11所有α值都为1,且k为偶数−1所有α值都为1,且k为奇数
-
那么该函数有什么作用以及如何求解呢?
-
作用如下(存疑?):可以求解
1~N
中和N
互质的数的个数,我们使用 S p , q S_{p,q} Sp,q表示表示1~N
中和N
存在公因数p
和q
的所有数的集合(其中p、q
都是质数),一共有 ⌊ N p ⌋ \lfloor \frac{N}{p} \rfloor ⌊pN⌋个数。则1~N
中和N
互质的数的个数为:
N − ∣ S 2 ∣ − ∣ S 3 ∣ − ∣ S 5 ∣ − . . . + ∣ S 2 , 3 ∣ + ∣ S 3 , 5 ∣ + . . . − ∣ S 2 , 3 , 5 ∣ − . . . N - |S_2| - |S_3| - |S_5| -... + |S_{2,3}| + |S_{3,5}| + ... - |S_{2,3,5}| -... N−∣S2∣−∣S3∣−∣S5∣−...+∣S2,3∣+∣S3,5∣+...−∣S2,3,5∣−...
观察可以发现,上式中除了N
之外的的其他项的系数就是莫比乌斯函数,因此上式可以改写成:
N
−
∑
μ
(
p
)
×
∣
S
p
∣
N - \sum \mu (p) \times |S_p|
N−∑μ(p)×∣Sp∣
- 如何求解
1~N
中每个数对应的莫比乌斯函数值呢?可以在质数线性筛法中求解,代码以及分析如下:
// 线性选法,时间复杂度:O(n)
void get_primes(int n) {
mobius[1] = 0;
for (int i = 2; i <= n; i++) {
if (!st[i]) {
primes[cnt++] = i;
mobius[i] = -1; // i本身就是质数,k为奇数
}
for (int j = 0; primes[j] <= n / i; j++) {
int t = primes[j] * i;
st[t] = true;
if (i % primes[j] == 0) {
mobius[t] = 0; // t至少存在两个质因子primes[j]
break;
}
// mobius[i] = 0 时, mobius[t]也为0
// mobius[i] != 0 时, 此时t比i多一个i质因数分解中不存在的质数, mobius[t]和mobius[i]异号
mobius[t] = mobius[i] * -1;
}
}
}
-
现在我们考虑本题如何求解,问题是给定我们
a、b、d
,我们需要计算数对的数量,这些数对(x, y)
需要满足: 1 ≤ x ≤ a , 1 ≤ y ≤ b 1 \le x \le a, 1 \le y \le b 1≤x≤a,1≤y≤b且gcd(x, y)==d
。 -
上述问题等价于: 1 ≤ x ′ ≤ ⌊ a d ⌋ , 1 ≤ y ′ ≤ ⌊ b d ⌋ 1 \le x' \le \lfloor \frac{a}{d} \rfloor, 1 \le y' \le \lfloor \frac{b}{d} \rfloor 1≤x′≤⌊da⌋,1≤y′≤⌊db⌋且
gcd(x', y')==1
的数对的数量。证明也很简单,只需要做一个简单的映射即可,即 x ′ = x d , y ′ = y d x' = \frac{x}{d}, y' = \frac{y}{d} x′=dx,y′=dy。 -
首先让 a = ⌊ a d ⌋ , b = ⌊ b d ⌋ a = \lfloor \frac{a}{d} \rfloor, b = \lfloor \frac{b}{d} \rfloor a=⌊da⌋,b=⌊db⌋。
-
现在问题转成了求互质的数的对数,这两个数的范围分别是从
1~a
,和从1~b
。可以使用容斥原理。最后的答案是:
a × b − ⌊ a 2 ⌋ × ⌊ b 2 ⌋ − ⌊ a 3 ⌋ × ⌊ b 3 ⌋ + ⌊ a 6 ⌋ × ⌊ b 6 ⌋ + . . . . . . a \times b - \lfloor \frac{a}{2} \rfloor \times \lfloor \frac{b}{2} \rfloor - \lfloor \frac{a}{3} \rfloor \times \lfloor \frac{b}{3} \rfloor + \lfloor \frac{a}{6} \rfloor \times \lfloor \frac{b}{6} \rfloor + ...... a×b−⌊2a⌋×⌊2b⌋−⌊3a⌋×⌊3b⌋+⌊6a⌋×⌊6b⌋+......
其中
⌊
a
2
⌋
×
⌊
b
2
⌋
\lfloor \frac{a}{2} \rfloor \times \lfloor \frac{b}{2} \rfloor
⌊2a⌋×⌊2b⌋代表a、b
存在公因数2
的数的对数。上式可以形式化的写成:
a
×
b
+
∑
i
=
1
m
i
n
(
a
,
b
)
μ
(
i
)
×
⌊
a
i
⌋
×
⌊
b
i
⌋
a \times b + \sum _ {i=1} ^ {min(a, b)} \mu (i) \times \lfloor \frac{a}{i} \rfloor \times \lfloor \frac{b}{i} \rfloor
a×b+i=1∑min(a,b)μ(i)×⌊ia⌋×⌊ib⌋
-
根据上式我们可以看出每次查询的时间复杂度是 O ( n ) O(n) O(n)的,因此总体的时间复杂度为 O ( n 2 ) O(n^2) O(n2),
n
最大为50000
,会超时。因此需要优化。 -
我们考虑对于给定正整数
n
,考虑 ⌊ n 1 ⌋ , ⌊ n 2 ⌋ , ⌊ n 3 ⌋ , . . . , ⌊ n n ⌋ \lfloor \frac{n}{1} \rfloor, \lfloor \frac{n}{2} \rfloor,\lfloor \frac{n}{3} \rfloor, ...,\lfloor \frac{n}{n} \rfloor ⌊1n⌋,⌊2n⌋,⌊3n⌋,...,⌊nn⌋中实际存在多少不同的数据,另外如何求解出这些数据的个数。 -
存在多少不同的数据:
-
实际上上述数据的个数不超过 2 × n 2 \times \sqrt n 2×n个,这是因为我们可以将数据分为两组:
(1) ⌊ n 1 ⌋ , ⌊ n 2 ⌋ , . . . , ⌊ n n ⌋ \lfloor \frac{n}{1} \rfloor, \lfloor \frac{n}{2} \rfloor, ...,\lfloor \frac{n}{\sqrt n} \rfloor ⌊1n⌋,⌊2n⌋,...,⌊nn⌋;
(2) ⌊ n n + 1 ⌋ , ⌊ n n + 2 ⌋ , . . . , ⌊ n n ⌋ \lfloor \frac{n}{\sqrt n + 1} \rfloor, \lfloor \frac{n}{\sqrt n +2} \rfloor, ...,\lfloor \frac{n}{n} \rfloor ⌊n+1n⌋,⌊n+2n⌋,...,⌊nn⌋。
-
第(1)部分只有 n \sqrt n n项;第(2)部分虽然项数很多,但是数值都是小于 n \sqrt n n的,因此最多有 n \sqrt n n项;因此不同的数据的个数不超过 2 × n 2 \times \sqrt n 2×n个。
-
如何求解出这些数据的个数:
-
我们首先考虑如下问题:对于
n
,给定我们一个小于等于n
的正整数x
,我们希望找到值等于 ⌊ n x ⌋ \lfloor \frac{n}{x} \rfloor ⌊xn⌋且最大的一个数y
满足 ⌊ n x ⌋ = ⌊ n y ⌋ \lfloor \frac{n}{x} \rfloor = \lfloor \frac{n}{y} \rfloor ⌊xn⌋=⌊yn⌋,我们可已看出y
是x
的函数,令 g ( x ) = y g(x)=y g(x)=y,例如给定n=24, x=9
,则g(x)=12
,因为 ⌊ 24 9 ⌋ = ⌊ 24 12 ⌋ \lfloor \frac{24}{9} \rfloor = \lfloor \frac{24}{12} \rfloor ⌊924⌋=⌊1224⌋,且12
是最大的一个。 -
g(x)
的形式化定义如下: ⌊ n x ⌋ = ⌊ n g ( x ) ⌋ \lfloor \frac{n}{x} \rfloor = \lfloor \frac{n}{g(x)} \rfloor ⌊xn⌋=⌊g(x)n⌋且 ⌊ n x ⌋ > ⌊ n g ( x ) + 1 ⌋ \lfloor \frac{n}{x} \rfloor > \lfloor \frac{n}{g(x) + 1} \rfloor ⌊xn⌋>⌊g(x)+1n⌋。这里的g(x)
是由公式解的,如下:
g ( x ) = ⌊ n ⌊ n x ⌋ ⌋ g(x) = \lfloor \frac{n}{\lfloor \frac{n}{x} \rfloor} \rfloor g(x)=⌊⌊xn⌋n⌋
- 我们可以证明
g(x)
是满足形式化定义中的两个式子的,首先证明: ⌊ n x ⌋ = ⌊ n g ( x ) ⌋ \lfloor \frac{n}{x} \rfloor = \lfloor \frac{n}{g(x)} \rfloor ⌊xn⌋=⌊g(x)n⌋。(证明方式:A>=B, A<=B
,则A==B
) - 根据
g(x)
的表达式,我们可以推出:
g ( x ) = ⌊ n ⌊ n x ⌋ ⌋ > = ⌊ n n x ⌋ = x g(x) = \lfloor \frac{n}{\lfloor \frac{n}{x} \rfloor} \rfloor >= \lfloor \frac{n}{\frac{n}{x}} \rfloor = x g(x)=⌊⌊xn⌋n⌋>=⌊xnn⌋=x
因此有:
⌊
n
x
⌋
<
=
⌊
n
g
(
x
)
⌋
(
1
)
\lfloor \frac{n}{x} \rfloor <= \lfloor \frac{n}{g(x)} \rfloor \quad (1)
⌊xn⌋<=⌊g(x)n⌋(1)
将g(x)
带入
⌊
n
g
(
x
)
⌋
\lfloor \frac{n}{g(x)} \rfloor
⌊g(x)n⌋,可以得到:
⌊
n
g
(
x
)
⌋
=
⌊
n
⌊
n
⌊
n
x
⌋
⌋
⌋
>
=
⌊
n
n
⌊
n
x
⌋
⌋
=
⌊
n
x
⌋
(
2
)
\lfloor \frac{n}{g(x)} \rfloor = \lfloor \frac{n}{\lfloor \frac{n}{\lfloor \frac{n}{x} \rfloor} \rfloor} \rfloor >= \lfloor \frac{n}{\frac{n}{\lfloor \frac{n}{x} \rfloor}} \rfloor = \lfloor \frac{n}{x} \rfloor \quad (2)
⌊g(x)n⌋=⌊⌊⌊xn⌋n⌋n⌋>=⌊⌊xn⌋nn⌋=⌊xn⌋(2)
因此(1)(2)式可知:
⌊
n
x
⌋
=
⌊
n
g
(
x
)
⌋
\lfloor \frac{n}{x} \rfloor = \lfloor \frac{n}{g(x)} \rfloor
⌊xn⌋=⌊g(x)n⌋。
- 下面接着证明形式化定义的第二个式子: ⌊ n x ⌋ > ⌊ n g ( x ) + 1 ⌋ \lfloor \frac{n}{x} \rfloor > \lfloor \frac{n}{g(x) + 1} \rfloor ⌊xn⌋>⌊g(x)+1n⌋。
- 令
n
=
k
×
x
+
r
,
0
≤
r
<
x
n = k \times x + r, 0 \le r < x
n=k×x+r,0≤r<x(对
x
求带余除法),则等价于证明下式(此时 g ( x ) = ⌊ n k ⌋ g(x) = \lfloor \frac{n}{k} \rfloor g(x)=⌊kn⌋):
k > ⌊ n ⌊ n k ⌋ + 1 ⌋ k > \lfloor \frac{n}{\lfloor \frac{n}{k} \rfloor + 1} \rfloor k>⌊⌊kn⌋+1n⌋
等价于证明:
k
×
(
⌊
n
k
⌋
+
1
)
>
n
k \times (\lfloor \frac{n}{k} \rfloor + 1) > n
k×(⌊kn⌋+1)>n
令
n
=
p
×
k
+
q
,
0
≤
q
<
k
n = p \times k + q, 0 \le q < k
n=p×k+q,0≤q<k(对k
求带余除法),带入上式可以得到:
k
×
(
p
+
1
)
>
p
×
k
+
q
⟺
k
>
q
k \times (p + 1) > p \times k + q \iff k > q
k×(p+1)>p×k+q⟺k>q
因此第二个式子也是成立的。
- 有了上述理论之后,我们就可以快速求解出刚开始推出来的式子:
a × b + ∑ i = 1 m i n ( a , b ) μ ( i ) × ⌊ a i ⌋ × ⌊ b i ⌋ a \times b + \sum _ {i=1} ^ {min(a, b)} \mu (i) \times \lfloor \frac{a}{i} \rfloor \times \lfloor \frac{b}{i} \rfloor a×b+i=1∑min(a,b)μ(i)×⌊ia⌋×⌊ib⌋
-
⌊ a i ⌋ × ⌊ b i ⌋ \lfloor \frac{a}{i} \rfloor \times \lfloor \frac{b}{i} \rfloor ⌊ia⌋×⌊ib⌋对应的序列有很多值都是相同的,计算一次就行,假设值记为
t
,对应的区间假设为[l, r]
,然后让t
乘以 μ ( l ) + μ ( l + 1 ) + . . . + μ ( r ) \mu(l)+\mu(l+1)+...+\mu(r) μ(l)+μ(l+1)+...+μ(r)即可求出该段中的和, μ ( l ) + μ ( l + 1 ) + . . . + μ ( r ) \mu(l)+\mu(l+1)+...+\mu(r) μ(l)+μ(l+1)+...+μ(r)可以使用前缀和求解。 -
时间复杂度为 O ( n × n ) O(n \times \sqrt n) O(n×n),
n
次询问,每次询问有不多于 2 × n 2 \times \sqrt n 2×n段数据相加。
代码
- C++
#include <iostream>
using namespace std;
typedef long long LL;
const int N = 50010;
int primes[N], cnt;
bool st[N];
int mobius[N], sum[N]; // sum是mobius的前缀和
// 线性筛法求莫比乌斯函数
void init(int n) {
mobius[1] = 1;
for (int i = 2; i <= n; i++) {
if (!st[i]) {
primes[cnt++] = i;
mobius[i] = -1;
}
for (int j = 0; primes[j] * i <= n; j++) {
int t = primes[j] * i;
st[t] = true;
if (i % primes[j] == 0) {
mobius[t] = 0;
break;
}
mobius[t] = mobius[i] * -1;
}
}
for (int i = 1; i <= n; i++) sum[i] = sum[i - 1] + mobius[i];
}
int main() {
init(N - 1);
int T;
scanf("%d", &T);
while (T--) {
int a, b, d;
scanf("%d%d%d", &a, &b, &d);
a /= d, b /= d;
int n = min(a, b);
LL res = 0;
for (int l = 1, r; l <= n; l = r + 1) {
r = min(n, min(a / (a / l), b / (b / l))); // 下次跳到的位置应该是r+1
res += (sum[r] - sum[l - 1]) * (LL) (a / l) * (b / l);
}
printf("%lld\n", res);
}
return 0;
}