只要你跑的够快,锅就追不上你

0%

「学习笔记」莫比乌斯反演进阶篇

杜教筛

杜教筛可以快速求一些积性函数的前缀和。假设我们要求 $S(n) = \sum_{i = 1}^{n} f(i)$,杜教筛方法如下:

对于积性函数 $f$,如果能找到积性函数 $g$,使得 $g, (f * g)$ 的前缀和都容易计算,那么我们就可以先用线性筛预处理出 $S(1), S(2), \cdots, S(\text{lim})$,然后使用整除分块和记忆化搜索计算 $S(n)$ $(n > \text{lim})$。当 $\text{lim} = n^{\frac{2}{3}}$ 时,算法的复杂度为 $O(n ^ {\frac{2}{3}})$,实际上 $\text{lim}$ 取得更大一点会更优。

例子:求 $\mu$ 的前缀和时,我们可以取 $g = 1$,这样 $(f * g) = \epsilon$,而 $1, \epsilon$ 的前缀和都可以快速计算。

YY 的 GCD

「Luogu 2257」YY 的 GCD

题目描述

$T$ 次询问 $n, m$,求:

其中 $P$ 表示素数集合。

数据范围:$T \le 10^4, n, m \le 10^7$。

思路分析

设数论函数 $p(n)$ 满足当 $n$ 为素数时 $p(n) = 1$,否则 $p(n) = 0$。那么答案为:

我们记 $f(n) = \sum_{d | n} \mu(d) p(\frac{n}{d})$,考虑枚举 $\frac{n}{d}$,这样就可以 $O(n \log \log n)$ 预处理出 $f(1), f(2), \cdots, f(n)$。然后用整除分块回答每组询问即可。时间复杂度 $O(n \log \log n + T \sqrt n)$。

代码实现

1
#include <bits/stdc++.h>
2
using namespace std;
3
4
typedef long long ll;
5
const int maxn = 1e7;
6
int T, n, m, cnt, prm[maxn + 3], mu[maxn + 3], f[maxn + 3];
7
bool vis[maxn + 3];
8
9
void prework(int n) {
10
	mu[1] = 1;
11
	for (int i = 2; i <= n; i++) {
12
		if (!vis[i]) {
13
			prm[++cnt] = i;
14
			mu[i] = -1;
15
		}
16
		for (int j = 1; j <= cnt && i * prm[j] <= n; j++) {
17
			vis[i * prm[j]] = true;
18
			if (i % prm[j] == 0) {
19
				mu[i * prm[j]] = 0;
20
				break;
21
			}
22
			mu[i * prm[j]] = -mu[i];
23
		}
24
	}
25
	for (int i = 1; i <= cnt; i++) {
26
		int x = prm[i];
27
		for (int j = 1; j * x <= n; j++) {
28
			f[j * x] += mu[j];
29
		}
30
	}
31
	for (int i = 1; i <= n; i++) {
32
		f[i] += f[i - 1];
33
	}
34
}
35
36
int main() {
37
	prework(maxn);
38
	scanf("%d", &T);
39
	while (T --> 0) {
40
		scanf("%d %d", &n, &m);
41
		ll ans = 0;
42
		for (int l = 1, r = 1; l <= min(n, m); l = r + 1) {
43
			r = min(n / (n / l), m / (m / l));
44
			ans += 1ll * (f[r] - f[l - 1]) * (n / l) * (m / l);
45
		}
46
		printf("%lld\n", ans);
47
	}
48
	return 0;
49
}

Siyuan GCD

「LOJ 6686」Stupid GCD

题目描述

给定 $n$,求:

数据范围:$n \le 10^{30}$。

思路分析

见我的博客:「LOJ 6686」Stupid GCD(数论)

代码实现

1
#include <bits/stdc++.h>
2
using namespace std;
3
4
typedef long long ll;
5
typedef __int128 lll;
6
const int maxn = 2e7, mod = 998244353;
7
int tot, a[100];
8
int cnt, prm[maxn / 10 + 3], phi[maxn + 3], pid[maxn + 3];
9
bool vis[maxn + 3];
10
map<ll, int> map_1, map_2;
11
ll m, p[100];
12
lll n;
13
14
void read(lll &x) {
15
	static char ch;
16
	static bool neg;
17
	for (ch = neg = 0; ch < '0' || ch > '9'; neg |= (ch == '-'), ch = getchar());
18
	for (x = 0; ch >= '0' && ch <= '9'; (x *= 10) += (ch ^ 48), ch = getchar());
19
	neg && (x = -x);
20
}
21
22
void dfs(int &ret, lll n, int dep, ll cur, ll num) {
23
	if (dep > tot) {
24
		ret = (ret + n / cur % mod * num) % mod;
25
		return;
26
	}
27
	dfs(ret, n, dep + 1, cur, num);
28
	cur *= p[dep], num *= p[dep] - 1;
29
	dfs(ret, n, dep + 1, cur, num);
30
	for (int i = 2; i <= a[dep]; i++) {
31
		cur *= p[dep], num *= p[dep];
32
		dfs(ret, n, dep + 1, cur, num);
33
	}
34
}
35
36
int solve_1(lll n, ll m) {
37
	int ret = m % mod;
38
	for (int i = 2; 1ll * i * i <= m; i++) {
39
		if (m % i == 0) {
40
			p[++tot] = i;
41
			while (m % i == 0) {
42
				m /= i, a[tot]++;
43
			}
44
		}
45
	}
46
	if (m > 1) {
47
		p[++tot] = m, a[tot] = 1;
48
	}
49
	dfs(ret, n, 1, 1, 1);
50
	return ret;
51
}
52
53
int func(int x) {
54
	return x < mod ? x : x - mod;
55
}
56
57
void prework(int n) {
58
	phi[1] = pid[1] = 1;
59
	for (int i = 2; i <= n; i++) {
60
		if (!vis[i]) {
61
			prm[++cnt] = i;
62
			phi[i] = i - 1;
63
		}
64
		pid[i] = 1ll * phi[i] * i % mod;
65
		for (int j = 1; j <= cnt && i * prm[j] <= n; j++) {
66
			vis[i * prm[j]] = true;
67
			if (i % prm[j] == 0) {
68
				phi[i * prm[j]] = phi[i] * prm[j];
69
				break;
70
			}
71
			phi[i * prm[j]] = phi[i] * (prm[j] - 1);
72
		}
73
	}
74
	for (int i = 2; i <= n; i++) {
75
		phi[i] = func(phi[i] + phi[i - 1]);
76
		pid[i] = func(pid[i] + pid[i - 1]);
77
	}
78
}
79
80
int func_1(ll n) {
81
	return (lll) n * (n + 1) / 2 % mod;
82
}
83
84
int func_2(ll n) {
85
	return (lll) n * (n + 1) * (n * 2 + 1) / 6 % mod;
86
}
87
88
int sum_1(ll n) {
89
	if (n <= maxn) {
90
		return phi[n];
91
	}
92
	if (map_1.count(n)) {
93
		return map_1[n];
94
	}
95
	int &ret = map_1[n] = func_1(n);
96
	for (ll l = 2, r = 2; l <= n; l = r + 1) {
97
		r = n / (n / l);
98
		ret = (ret + 1ll * (r - l + 1) % mod * (mod - sum_1(n / l))) % mod;
99
	}
100
	return ret;
101
}
102
103
int sum_2(ll n) {
104
	if (n <= maxn) {
105
		return pid[n];
106
	}
107
	if (map_2.count(n)) {
108
		return map_2[n];
109
	}
110
	int &ret = map_2[n] = func_2(n);
111
	for (ll l = 2, r = 2; l <= n; l = r + 1) {
112
		r = n / (n / l);
113
		ret = (ret + 1ll * func(func_1(r) - func_1(l - 1) + mod) * (mod - sum_2(n / l))) % mod;
114
	}
115
	return ret;
116
}
117
118
int solve_2(ll n) {
119
	prework(min((ll) maxn, n));
120
	int ans = 0;
121
	for (ll l = 1, r = 1; l <= n; l = r + 1, r = 0) {
122
		r = n / (n / l);
123
		ans = (ans + 1ll * (sum_1(r) - sum_1(l - 1) + mod) * func_1(n / l)) % mod;
124
		ans = (ans + 1ll * (sum_2(r) - sum_2(l - 1) + mod) * func_2(n / l)) % mod;
125
	}
126
	return (3ll * ans + func_1(n)) % mod;
127
}
128
129
int main() {
130
	read(n);
131
	m = powl(n + .5, 1. / 3);
132
	while ((lll) m * m * m > n) m--;
133
	while ((lll) (m + 1) * (m + 1) * (m + 1) <= n) m++;
134
	printf("%d\n", func(solve_1(n - (lll) m * m * m, m) + solve_2(m - 1)));
135
	return 0;
136
}

下面原本写了 4 道题的题解,然后因为 bug 没掉了,所以这里引用黄队的博客中较重要的两题,并稍作修改。

密码解锁

「Luogu 5348」密码解锁

题目描述

已知:

求 $f(m)$。

数据范围:$m\le 10^9,\dfrac{n}{m}\le 10^9$。

思路分析

由莫比乌斯反演推论可以得到:

考虑把这个和式做一个递归:

递归边界为 $S(n,1)=\sum_{i=1}^n\mu^2(i)$。我们考虑 $\mu^2(i)$ 的贡献。当且仅当 $i$ 不含平方及以上的因子时,$\mu^2(i)=1$,否则 $\mu^2(i)=0$。

因此我们要求 $[1, n]$ 中无平方因子的数的个数。那么考虑容斥,用整体减掉含有平方因子的数:

$\mu(i)$ 是容斥系数。我们记忆化计算 $S$ 即可,时间复杂度不超过 $O(\sqrt{\frac{n}{m}} \sigma_0(m))$,可以通过。

代码实现

1
#include <bits/stdc++.h>
2
using namespace std;
3
4
typedef long long ll;
5
const int maxn = 1e7;
6
int T, n, m, cnt, prm[maxn + 3], mu[maxn + 3], tot, p[100], a[100];
7
bool vis[maxn + 3];
8
map<int, int> M_mu;
9
map<pair<int, int>, int> M;
10
ll k;
11
12
void prework(int n) {
13
	mu[1] = 1;
14
	for (int i = 2; i <= n; i++) {
15
		if (!vis[i]) {
16
			prm[++cnt] = i;
17
			mu[i] = -1;
18
		}
19
		for (int j = 1; j <= cnt && i * prm[j] <= n; j++) {
20
			vis[i * prm[j]] = true;
21
			if (i % prm[j] == 0) {
22
				mu[i * prm[j]] = 0;
23
				break;
24
			}
25
			mu[i * prm[j]] = -mu[i];
26
		}
27
	}
28
}
29
30
int get_mu(int x) {
31
	return x <= maxn ? mu[x] : M_mu[x];
32
}
33
34
void dfs(int x, int cur, int val) {
35
	if (x > tot) {
36
		if (cur > maxn) {
37
			M_mu[cur] = val;
38
		}
39
		return;
40
	}
41
	dfs(x + 1, cur, val);
42
	cur *= p[x], val *= -1;
43
	dfs(x + 1, cur, val);
44
	for (int i = 2; i <= a[x]; i++) {
45
		cur *= p[x];
46
		dfs(x + 1, cur, 0);
47
	}
48
}
49
50
int S(int n, int m) {
51
	if (n == 0) {
52
		return 0;
53
	}
54
	if (M.count(make_pair(n, m))) {
55
		return M[make_pair(n, m)];
56
	}
57
	int &ret = M[make_pair(n, m)] = 0;
58
	if (m == 1) {
59
		for (int i = 1; i * i <= n; i++) {
60
			ret += n / (i * i) * mu[i];
61
		}
62
	} else {
63
		for (int i = 1; i * i <= m; i++) {
64
			if (m % i == 0) {
65
				ret += get_mu(i) * S(n / i, i);
66
			}
67
		}
68
		for (int i = 1; i * i < m; i++) {
69
			if (m % i == 0) {
70
				ret += get_mu(m / i) * S(n / (m / i), m / i);
71
			}
72
		}
73
	}
74
	return ret;
75
}
76
77
int main() {
78
	prework(maxn);
79
	scanf("%d", &T);
80
	while (T --> 0) {
81
		scanf("%lld %d", &k, &m);
82
		n = k / m;
83
		tot = 0;
84
		int cur = m;
85
		for (int i = 2; i * i <= cur; i++) {
86
			if (cur % i == 0) {
87
				p[++tot] = i, a[tot] = 0;
88
				while (cur % i == 0) {
89
					a[tot]++, cur /= i;
90
				}
91
			}
92
		}
93
		if (cur > 1) {
94
			p[++tot] = cur, a[tot] = 1;
95
		}
96
		dfs(1, 1, 1);
97
		printf("%d\n", S(n, m) * get_mu(m));
98
	}
99
	return 0;
100
}

FSF’s Game

「HDU 4944」FSF’s Game

题目描述

给定 $n$,求:

数据范围:$T,n\le 5\times 10^5$。

思路分析

设 $f(n)=\sum_{i=1}^n\sum_{j=i}^n\operatorname{lcm}(i,j)=\sum_{j=1}^n\sum_{i=1}^j\operatorname{lcm}(i,j)$。

设 $g(n)=\sum_{i=1}^n\gcd(n,i)$。那么可以用两两配对的思路反演得到:

那么我们做一个 $g(n)$ 的前缀和就得到了 $f(n)$。现在原式化简为了:

发现对于 $f(i)$,它对区间 $[ij, (i + 1)j - 1]$ 中的 $F$ 有 $f(i) \times j^2$ 的贡献。枚举 $i, j$,使用前缀和优化来预处理即可,时间复杂度 $O(n \log n)$。

代码实现

1
#include <bits/stdc++.h>
2
using namespace std;
3
4
typedef unsigned ui;
5
typedef long long ll;
6
const int maxn = 5e5;
7
int T, cnt, prm[maxn + 3];
8
ll num[maxn + 3], iphi[maxn + 3];
9
ui lcm[maxn + 3], res[maxn + 3];
10
bool vis[maxn + 3];
11
12
void prework(int n) {
13
	iphi[1] = 1;
14
	for (int i = 2; i <= n; i++) {
15
		if (!vis[i]) {
16
			prm[++cnt] = i;
17
			iphi[i] = 1ll * i * (i - 1);
18
		}
19
		for (int j = 1; j <= cnt && i * prm[j] <= n; j++) {
20
			vis[i * prm[j]] = true;
21
			if (i % prm[j] == 0) {
22
				iphi[i * prm[j]] = iphi[i] * prm[j] * prm[j];
23
				break;
24
			}
25
			iphi[i * prm[j]] = iphi[i] * prm[j] * (prm[j] - 1);
26
		}
27
	}
28
	for (int i = 1; i <= n; i++) {
29
		for (int j = i; j <= n; j += i) {
30
			num[j] += iphi[i];
31
		}
32
	}
33
	for (int i = 1; i <= n; i++) {
34
		num[i] = (num[i] + 1) * i / 2;
35
		lcm[i] = lcm[i - 1] + num[i];
36
	}
37
	for (int i = 1; i <= n; i++) {
38
		for (int j = 1; i * j <= n; j++) {
39
			res[i * j] += lcm[i] * (ui) j * (ui) j;
40
			res[min(n + 1, (i + 1) * j)] -= lcm[i] * (ui) j * (ui) j;
41
		}
42
	}
43
	for (int i = 1; i <= n; i++) {
44
		res[i] += res[i - 1];
45
	}
46
}
47
48
int main() {
49
	prework(maxn);
50
	scanf("%d", &T);
51
	for (int x, _ = 1; _ <= T; _++) {
52
		scanf("%d", &x);
53
		printf("Case #%d: %u\n", _, res[x]);
54
	}
55
	return 0;
56
}