Project Euler 379

Project Euler 379

题目

Least common multiple count

Let \(f(n)\) be the number of couples \((x,y)\) with \(x\) and \(y\) positive integers, \(x \le y\) and the least common multiple of \(x\) and \(y\) equal to \(n\).

Let \(g\) be the summatory function of \(f\), i.e.: \(g(n) = \sum f(i)\) for \(1 \le i \le n\).

You are given that \(g(10^6) = 37429395\).

Find \(g(10^{12})\).

解决方案

\(N=10^{12}\)。把 \(n\) 分解为素因子:\(\displaystyle{n=\prod_{i=1}^t p_i^{e_i}.}\)那么写\(\displaystyle{x=\prod_{i=1}^t p_i^{a_i}, y=\prod_{i=1}^t p_i^{b_i}, a_i,b_i\ge 0.}\)\(\displaystyle{\mathrm{lcm}(x,y)=\prod_{i=1}^t p_i^{\max(a_i,b_i)}.}\) 因此 \(\mathrm{lcm}(x,y)=n\) 当且仅当对每个素数 \(p_i\) 都有\(\max(a_i,b_i)=e_i.\)

固定某个 \(p^{e}\)。满足 \(\max(a,b)=e\) 的有序对 \((a,b)\) 个数为:

  • 总共有 \((e+1)^2\)\((a,b)\in[0,e]^2\)
  • 去掉 \(\max(a,b)\le e-1\) 的,即 \([0,e-1]^2\)\(e^2\) 个。

所以该素数贡献为 \((e+1)^2-e^2=2e+1\)。不同素数独立相乘,得到有序 \((x,y)\) 个数:\(\displaystyle{F(n)=\prod_{i=1}^t (2e_i+1).}\)

但注意\(n^2\)的因子个数恰好是\(\displaystyle{\tau(n^2)=\prod_{i=1}^t (2e_i+1),}\)因此\(F(n)=\tau(n^2).\)

\((x,y)\) 是解,则 \((y,x)\) 也是解;除非 \(x=y\)。唯一的对角线解是 \((n,n)\)。因此把有序对对半并修正对角线:\(f(n)=\dfrac{\tau(n^2)+1}{2}.\)

由上式\(\displaystyle{g(n)=\sum_{k=1}^n f(k)=\frac{1}{2}\sum_{k=1}^n \bigl(\tau(k^2)+1\bigr)=\frac{n+\sum_{k=1}^n \tau(k^2)}{2}}\).记\(\displaystyle{S(n)=\sum_{k=1}^n \tau(k^2),}\)\(g(n)=\dfrac{n+S(n)}{2}.\)接下来核心就是高效计算 \(S(N)\)

\(\mu\) 为 Möbius 函数,\(\mu^2(n)\) 是“无平方因子(squarefree)指示函数”(若 \(n\) 是无平方因子数则为 \(1\),否则为 \(0\))。

那么有如下狄利克雷卷积

\[ \tau(n^2)=(\tau \ast \mu^2)(n)=\sum_{d\mid n}\tau(d)\mu^2\left(\frac{n}{d}\right). \]

证明很直接:\(\mu^2(n)\)\(\tau(d)\)都是乘法函数,因此只需验证素数幂 \(n=p^a\)的情况:\((\tau*\mu^2)(p^a)=\tau(p^a)\mu^2(1)+\tau(p^{a-1})\mu^2(p)=(a+1)\cdot 1+a\cdot 1=2a+1=\tau(p^{2a})=\tau((p^a)^2).\)最终得证。

由上述恒等式可得:\(\displaystyle{S(n)=\sum_{m\le n}\tau(m^2)=\sum_{m\le n}\sum_{d\mid m}\tau(d)\mu^2\left(\frac{m}{d}\right).}\)\(m=d\cdot k\),交换求和次序,有\(\displaystyle{S(n)=\sum_{k\le n}\mu^2(k)\sum_{d\le n/k}\tau(d).}\)

定义“约数函数的前缀和”:\(\displaystyle{D(x)=\sum_{i\le x}\tau(i).}\)于是得到非常关键的形式:

\[ S(n)=\sum_{k\le n}\mu^2(k)D\left(\left\lfloor\frac{n}{k}\right\rfloor\right). \]

这启发我们使用数论分块解决这道题目。当 \(k\) 变化时,\(\left\lfloor n/k\right\rfloor\) 只会取 \(O(\sqrt n)\) 种不同值。令\(q=\left\lfloor\dfrac{n}{k}\right\rfloor, r=\left\lfloor\dfrac{n}{q}\right\rfloor,\)则对所有 \(k\in[k_0,r]\) 都有相同的商 \(q\)。因此\(\displaystyle{\sum_{k=k_0}^{r}\mu^2(k)=Q(r)-Q(k_0-1),}\)

其中\(\displaystyle{Q(x)=\sum_{i\le x}\mu^2(i)}\)是squarefree 计数函数。

于是我们把\(S(n)\)分成一个个块,其中每个 block 对应一段连续的 \(k\) 使得 \(\lfloor n/k\rfloor\) 相同。

为计算\(D(x)\),有\(\displaystyle{D(x)=\sum_{i\le x}\tau(i)=\sum_{a=1}^{x}\left\lfloor\frac{x}{a}\right\rfloor=2\sum_{a=1}^{\lfloor\sqrt x\rfloor}\left\lfloor\frac{x}{a}\right\rfloor-\lfloor\sqrt x\rfloor^2.}\)

这样也可以使用数论分块完成完成。

为计算\(Q(x)\),利用容斥原理,可得无平方因子数的个数满足\(\displaystyle{Q(x)=\sum_{d=1}^{\lfloor\sqrt x\rfloor} \mu(d)\left\lfloor\frac{x}{d^2}\right\rfloor.}\) 为了让多次查询更快,可以把 \(d\) 在立方根处截断:令 \(y=\lfloor x^{1/3}\rfloor\)

  • \(d\le y\):直接累加 \(\mu(d)\left\lfloor x/d^2\right\rfloor\)
  • \(d>y\):此时 \(v=\left\lfloor x/d^2\right\rfloor \le \left\lfloor x/(y+1)^2\right\rfloor = O(x^{1/3})\)。把相同的 \(v\) 归并:\(\left\lfloor\dfrac{x}{d^2}\right\rfloor=v\iff d\in\left(\sqrt{\dfrac{x}{v+1}},\sqrt{\dfrac{x}{v}}\right].\)若预先有 Mertens 前缀\(\displaystyle{M(t)=\sum_{i\le t}\mu(i),}\)则该段内 \(\sum \mu(d)=M(d_{\max})-M(d_{\min}-1)\),从而这一部分在 \(O(x^{1/3})\) 内算完。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
#include <bits/stdc++.h>
using namespace std;

typedef long long ll;

// -------------------- globals (pure-function style) --------------------
ll G_N = 0;
int G_LIMIT = 0;

vector<int8_t> G_mu; // mu[i]
vector<uint32_t> G_Qpref; // sum_{k<=i} mu(k)^2
vector<ll> G_Dpref; // sum_{k<=i} tau(k)
vector<int> G_Mertens; // M(t)=sum_{k<=t} mu(k), t<=sqrt(N)

unordered_map<ll, ll> G_cacheD;
unordered_map<ll, ll> G_cacheQ;

// -------------------- integer roots --------------------
inline ll isqrt_ll(ll x)
{
long double r = sqrt((long double)x);
ll y = (ll)r;
while ((__int128)(y + 1) * (y + 1) <= x)
++y;
while ((__int128)y * y > x)
--y;
return y;
}

inline ll icbrt_ll(ll x)
{
long double r = cbrt((long double)x);
ll y = (ll)llround(r);
auto cube = [](ll t) -> __int128
{ return (__int128)t * t * t; };
while (cube(y + 1) <= x)
++y;
while (cube(y) > x)
--y;
return y;
}

// -------------------- sieve: mu, tau, prefixes --------------------
void build_sieve(int limit)
{
// Linear sieve
vector<uint32_t> lp(limit + 1, 0); // least prime factor
vector<uint16_t> tau(limit + 1, 0); // divisor count
vector<uint8_t> exp(limit + 1, 0); // exponent of lp in i
vector<int> primes;
primes.reserve(limit / 10);

G_mu.assign(limit + 1, 0);
G_mu[1] = 1;
tau[1] = 1;

for (int i = 2; i <= limit; ++i)
{
if (lp[i] == 0)
{
lp[i] = (uint32_t)i;
primes.push_back(i);
G_mu[i] = -1;
tau[i] = 2;
exp[i] = 1;
}

uint32_t li = lp[i];
uint8_t ei = exp[i];
uint16_t ti = tau[i];
int8_t mui = G_mu[i];

for (int p : primes)
{
long long x = 1LL * i * p;
if (x > limit)
break;

lp[(int)x] = (uint32_t)p;

if ((uint32_t)p == li)
{
// p divides i already
G_mu[(int)x] = 0;
uint8_t e = (uint8_t)(ei + 1);
exp[(int)x] = e;
// tau(i*p) = tau(i) / (ei+1) * (e+1)
tau[(int)x] = (uint16_t)((ti / (ei + 1)) * (e + 1));
break;
}
else
{
G_mu[(int)x] = (int8_t)(-mui);
exp[(int)x] = 1;
tau[(int)x] = (uint16_t)(ti * 2);
}
}
}

// Prefixes
G_Qpref.assign(limit + 1, 0);
G_Dpref.assign(limit + 1, 0);

uint32_t sQ = 0;
ll sD = 0;
for (int i = 1; i <= limit; ++i)
{
if (G_mu[i] != 0)
++sQ; // mu^2(i)=1 iff mu(i)!=0
sD += tau[i];
G_Qpref[i] = sQ;
G_Dpref[i] = sD;
}
}

// -------------------- Mertens prefix up to sqrt(N) --------------------
void build_mertens_prefix(int maxT)
{
G_Mertens.assign(maxT + 1, 0);
int s = 0;
for (int i = 1; i <= maxT; ++i)
{
s += (int)G_mu[i];
G_Mertens[i] = s;
}
}

// -------------------- D(x) = sum_{i<=x} tau(i) = sum_{k<=x} floor(x/k) --------------------
inline ll divisorSummatory(ll x)
{
if (x <= G_LIMIT)
return (ll)G_Dpref[(int)x];

auto it = G_cacheD.find(x);
if (it != G_cacheD.end())
return it->second;

// quotient grouping: O(#distinct floor(x/k)) ~ O(sqrt(x))
ll ans = 0;
ll l = 1;
while (l <= x)
{
ll v = x / l;
ll r = x / v;
ans += v * (r - l + 1);
l = r + 1;
}

G_cacheD.emplace(x, ans);
return ans;
}

// -------------------- Q(x) = sum_{i<=x} mu(i)^2 = #squarefree <= x --------------------
inline ll squarefreeCount(ll x)
{
if (x <= G_LIMIT)
return (ll)G_Qpref[(int)x];

auto it = G_cacheQ.find(x);
if (it != G_cacheQ.end())
return it->second;

// Q(x)=sum_{d<=sqrt(x)} mu(d)*floor(x/d^2)
// cube-root split + Mertens block for d>cuberoot(x)
ll y = icbrt_ll(x);

ll sum = 0;
for (ll d = 1; d <= y; ++d)
{
sum += (ll)G_mu[(int)d] * (x / (d * d));
}

ll yy = y + 1;
ll vmax = x / (yy * yy);
for (ll v = 1; v <= vmax; ++v)
{
ll hi = isqrt_ll(x / v);
if (hi <= y)
break;

ll lo = isqrt_ll(x / (v + 1)) + 1;
if (lo <= y)
lo = y + 1;

// sum_{d in [lo..hi]} mu(d) = M(hi)-M(lo-1)
sum += v * ((ll)G_Mertens[(int)hi] - (ll)G_Mertens[(int)(lo - 1)]);
}

G_cacheQ.emplace(x, sum);
return sum;
}

// -------------------- main solver: g(n) = (n + S(n))/2 --------------------
ll solve_g(ll n, int limit = 15'000'000)
{
G_N = n;
G_LIMIT = limit;

build_sieve(G_LIMIT);

ll sqrt_n = isqrt_ll(G_N);
build_mertens_prefix((int)sqrt_n);

G_cacheD.clear();
G_cacheQ.clear();
G_cacheD.reserve(1 << 20);
G_cacheQ.reserve(1 << 20);

// S(n)=sum_{k<=n} mu(k)^2 * D(floor(n/k)) via quotient blocks
ll S = 0;
ll k = 1;
ll prevQ = 0; // Q(k-1)

while (k <= G_N)
{
ll q = G_N / k;
ll k2 = G_N / q;

ll Qk2 = squarefreeCount(k2);
ll cnt_sqfree = Qk2 - prevQ;

S += cnt_sqfree * divisorSummatory(q);

prevQ = Qk2;
k = k2 + 1;
}

return (G_N + S) / 2;
}

int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);

cout << solve_g(1'000'000'000'000LL) << "\n";
return 0;
}