整除分块:
好吧这就是一个前置小技巧,来看这样一个式子$$\sum_{i=1}^{n} \lfloor \frac{n}{i} \rfloor$$ 利用整除分块的方法,我们可以在$O(\sqrt(n))$的时间内计算出这个式子
容易发现,有许多$\lfloor \frac{n}{i} \rfloor$的值是一样的,而且它们都呈块状分布,经过一些小计算后可以发现,每一块的右边界即为$n/(n/l)$,于是我们就可以快速计算了
1 for(int l = 1, r; l <= n; l = r + 1)
2 {
3 r = n / (n / l);
4 ans += (r - l + 1) * (n / l);
5 }
莫比乌斯函数:
来看一些有趣的性质:
- 对于任意正整数$n$,$\sum_{d|n} \mu(d) = [n = 1]$
- 对于任意正整数$n$,$\sum_{d|n} \frac{\mu(d)}{d} = \frac{\phi(n)}{n}$
基于线性筛素数的方法,我们可以写出线性筛莫比乌斯函数的代码:
1 void get_mu(int n)
2 {
3 mu[1] = 1;
4 for(int i = 2; i <= n; ++i)
5 {
6 if(!vis[i]) {prim[++cnt] = i; mu[i] = -1;}
7 for(int j = 1; j <= cnt && prim[j] * i <= n; ++j)
8 {
9 vis[prim[j] * i] = 1;
10 if(i % prim[j] == 0) break;
11 mu[i * prim[j]] = -mu[i];
12 }
13 }
14 }
莫比乌斯反演:
- 定理:$F(n)$和$f(n)$是定义在非负整数集合上的两个函数,并且满足条件:$$F(n) = \sum_{d|n} f(d)$$
那么我们有结论:$$f(n) = \sum_{d|n} \mu(d) F(\lfloor \frac{n}{d} \rfloor)$$
这就是所谓的莫比乌斯反演定理
- 证明:$$f(n) = \sum_{d|n} \mu(d) F(\lfloor \frac{n}{d} \rfloor) = \sum_{d|n} \mu(d) \sum_{k|\frac{n}{d}}f(k) = \sum_{k|n}f(k) \sum_{d|\frac{n}{k}} \mu(d)$$
由上面的性质1可知,当且仅当$\frac{n}{k} = 1$时,即$k = n$时,$\sum_{d|\frac{n}{k}} \mu(d) = 1$,其余时候都为0,由此可知定理成立
- 莫比乌斯反演还有另外一种常用的形式,若满足条件:$$F(n) = \sum_{n|d}f(d)$$
有结论:$$f(n) = \sum_{n|d} \mu(\frac{d}{n}) F(d)$$
例题:
设小于$min(n, m)$的质数为$p_1, p_2...p_k$,题目所求即为:
$$Ans = \sum_{x = 1}^{n} \sum_{y = 1}^{m} \sum_{i = 1}^{k} [gcd(x, y) = p_i]$$
设$f(d)$表示满足$gcd(x, y) = d$且$1≤x≤n, 1≤y≤m$的$(x, y)$的对数
设$F(d)$表示满足$d|gcd(x, y)$且$1≤x≤n, 1≤y≤m$的$(x, y)$的对数
显然有:
$$F(x) = \sum_{x|d}^{\min(n, m)} f(d) = \lfloor \frac{n}{x} \rfloor \lfloor \frac{m}{x} \rfloor$$
反演一下可得:
$$f(x) = \sum_{x|d}^{\min(n, m)} \mu(\frac{d}{x}) F(d)$$
我们所求的答案即为:
$$Ans = \sum_{i = 1}^{k} f(p_i) = \sum_{i = 1}^{k} \sum_{p_i|d}^{\min(n, m)} \mu(\frac{d}{p_i}) F(d)$$
观察上式,发现$p_i$的倍数只有$\lfloor \frac{\min(n, m)}{p_i} \rfloor$个,因此我们更改枚举的对象为$p_i$的系数:
$$Ans = \sum_{i = 1}^{k} \sum_{d = 1}^{\lfloor \frac{\min(n, m)}{p_i} \rfloor} \mu(d) F(dp_i) = \sum_{i = 1}^{k} \sum_{d = 1}^{\lfloor \frac{\min(n, m)}{p_i} \rfloor} \mu(d) \lfloor \frac{n}{dp_i} \rfloor \lfloor \frac{m}{dp_i} \rfloor$$
令$T = dp_i$,枚举一下$T$,提到前面去,即:
$$Ans = \sum_{T = 1}^{\min(n, m)} \lfloor \frac{n}{T} \rfloor \lfloor \frac{m}{T} \rfloor \sum_{k|T, k∈prime} \mu(\frac{T}{k})$$
利用整除分块的技巧,我们首先用线性筛预处理出$\sum_{k|T, k∈prime} \mu(\frac{T}{k})$的前缀和,然后把取值相同的$\lfloor \frac{n}{T} \rfloor \lfloor \frac{m}{T} \rfloor$放在一起计算
1 // luogu-judger-enable-o2
2 #include<bits/stdc++.h>
3 using namespace std;
4
5 const int MAXN = 10000010;
6
7 int cnt, prime[MAXN], mu[MAXN], g[MAXN];
8 bool vis[MAXN]; long long sum[MAXN];
9
10 void get_mu(int n)
11 {
12 mu[1] = 1;
13 for(int i = 2; i <= n; ++i)
14 {
15 if(!vis[i]) {prime[++cnt] = i; mu[i] = -1;}
16 for(int j = 1; j <= cnt && i * prime[j] <= n; ++j)
17 {
18 vis[i * prime[j]] = 1;
19 if(i % prime[j] == 0) break;
20 mu[i * prime[j]] = -mu[i];
21 }
22 }
23 for(int j = 1; j <= cnt; ++j)
24 for(int i = 1; i * prime[j] <= n; ++i)
25 g[i * prime[j]] += mu[i];
26 for(int i = 1; i <= n; ++i) sum[i] = sum[i - 1] + g[i];
27 }
28
29 int T, n, m;
30 long long ans;
31
32 int main()
33 {
34 get_mu(MAXN - 10);
35 scanf("%d", &T);
36 while(T--)
37 {
38 scanf("%d %d", &n, &m);
39 if(n > m) swap(n, m); ans = 0;
40 for(int l = 1, r; l <= n; l = r + 1)
41 {
42 r = min(n / (n / l), m / (m / l));
43 ans += (long long)(n / l) * (m / l) * (sum[r] - sum[l - 1]);
44 }
45 printf("%lld\n", ans);
46 }
47 return 0;
48 }
有一个结论:$d(i,j) = \sum_{x|i} \sum_{y|j} [gcd(x, y) = 1]$,证明留给读者作习题(我也不会)
那么题目就是求$$\sum_{i = 1}^{n} \sum_{j = 1}^{m} \sum_{x|i} \sum_{y|j} [gcd(x, y) = 1]$$
把枚举对象换一下($i$换成$x$,$j$换成$y$)就是:$$\sum_{i = 1}^{n} \sum_{j = 1}^{m} \lfloor \frac{n}{i} \rfloor \lfloor \frac{m}{j} \rfloor [gcd(i, j) = 1]$$
看上去可以反演了,我们设:
$$f(x) = \sum_{i = 1}^{n} \sum_{j = 1}^{m} \lfloor \frac{n}{i} \rfloor \lfloor \frac{m}{j} \rfloor [gcd(i, j) = x]$$
$$F(x) = \sum_{x|d} f(d)$$
我们所求的答案即为:
$$Ans = f(1) = \sum_{d = 1}^{min(n, m)} \mu(d) F(d)$$
现在来看$F(x)$:
$$F(x) = \sum_{i = 1}^{n} \sum_{j = 1}^{m} \lfloor \frac{n}{i} \rfloor \lfloor \frac{m}{j} \rfloor [x | gcd(i, j)]$$
把$x$提出来就可以忽略掉$gcd$了:
$$F(x) = \sum_{i = 1}^{\lfloor \frac{n}{x} \rfloor} \sum_{j = 1}^{\lfloor \frac{m}{x} \rfloor} \lfloor \frac{n}{ix} \rfloor \lfloor \frac{m}{jx} \rfloor$$
我们只要预处理出$sum[x] = \sum_{i = 1}^x \lfloor \frac{x}{i} \rfloor$即可$O(1)$计算$F(x)$
1 #include<bits/stdc++.h>
2 using namespace std;
3
4 const int MAXN = 50010;
5
6 int cnt, prime[MAXN], mu[MAXN], sum[MAXN];
7 bool vis[MAXN];
8
9 void init(int n)
10 {
11 mu[1] = 1;
12 for(int i = 2; i <= n; ++i)
13 {
14 if(!vis[i]) {prime[++cnt] = i; mu[i] = -1;}
15 for(int j = 1; j <= cnt && prime[j] * i <= n; ++j)
16 {
17 vis[i * prime[j]] = 1;
18 if(i % prime[j] == 0) break;
19 mu[i * prime[j]] = -mu[i];
20 }
21 }
22 for(int i = 1; i <= n; ++i) mu[i] += mu[i - 1];
23 for(int x = 1; x <= n; ++x)
24 for(int l = 1, r; l <= x; l = r + 1)
25 {
26 r = x / (x / l);
27 sum[x] += (r - l + 1) * (x / l);
28 }
29 }
30
31 int T, n, m;
32 long long ans;
33
34 int main()
35 {
36 init(MAXN - 10);
37 scanf("%d", &T);
38 while(T--)
39 {
40 scanf("%d %d", &n, &m);
41 if(n > m) swap(n, m); ans = 0;
42 for(int l = 1, r; l <= n; l = r + 1)
43 {
44 r = min(n / (n / l), m / (m / l));
45 ans += (long long)(mu[r] - mu[l - 1]) * sum[n / l] * sum[m / l];
46 }
47 printf("%lld\n", ans);
48 }
49 return 0;
50 }
首先用二维前缀和搞一下,把式子拆成四部分:$(a,b,c,d) = (1,b,1,d) - (1,a-1,1,d) - (1,b,1,c-1) + (1,a-1,1,c-1)$
然后有了第一个例题的经验,我们很容易反演到这一步:
$$f(x) = \sum_{d=1}^{\min(\lfloor \frac{n}{x} \rfloor, \lfloor \frac{m}{x} \rfloor)} \mu(d) \lfloor \frac{n}{dx} \rfloor \lfloor \frac{m}{dx} \rfloor$$
到了这一步就可以了,如果还像第一题那样继续推的话,你就会发现那样你需要预处理出$\mu$除以所有$k$的前缀和,空间和时间都无法接受
计算的时候注意乘法爆$int$的问题,我因为这个$WA$了两遍
1 #include<bits/stdc++.h>
2 using namespace std;
3
4 const int MAXN = 50010;
5 typedef long long ll;
6
7 int mu[MAXN], cnt, prime[MAXN];
8 bool vis[MAXN];
9
10 void get_mu(int n)
11 {
12 mu[1] = 1;
13 for(int i = 2; i <= n; ++i)
14 {
15 if(!vis[i]) prime[++cnt] = i, mu[i] = -1;
16 for(int j = 1; j <= cnt && i * prime[j] <= n; ++j)
17 {
18 vis[i * prime[j]] = 1;
19 if(i % prime[j] == 0) break;
20 mu[i * prime[j]] = -mu[i];
21 }
22 }
23 for(int i = 1; i <= n; ++i) mu[i] += mu[i - 1];
24 }
25
26 ll Calc(int n, int m, int k)
27 {
28 ll ans = 0; if(n > m) swap(n, m);
29 for(int l = 1, r; l <= n / k; l = r + 1)
30 {
31 r = min((n / k) / (n / k / l), (m / k) / (m / k / l));
32 ans += (long long)(mu[r] - mu[l - 1]) * ((long long)(n / k / l) * (m / k / l));
33 }
34 return ans;
35 }
36
37 int main()
38 {
39 get_mu(MAXN - 10);
40 int n, a, b, c, d, k;
41 scanf("%d", &n);
42 while(n--)
43 {
44 scanf("%d %d %d %d %d", &a, &b, &c, &d, &k);
45 printf("%lld\n", Calc(b, d, k) - Calc(a - 1, d, k) - Calc(b, c - 1, k) + Calc(a - 1, c - 1, k));
46 }
47 return 0;
48 }
来源:oschina
链接:https://my.oschina.net/u/4336860/blog/3650690