杜教筛
杜教筛可以快速求一些积性函数的前缀和。假设我们要求 $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
题目描述
$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 |
|
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
题目描述
给定 $n$,求:
数据范围:$n \le 10^{30}$。
思路分析
见我的博客:「LOJ 6686」Stupid GCD(数论)
代码实现
1 |
|
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 没掉了,所以这里引用黄队的博客中较重要的两题,并稍作修改。
密码解锁
题目描述
已知:
求 $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 |
|
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
题目描述
给定 $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 |
|
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 | } |