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;ll G_N = 0 ; int G_LIMIT = 0 ;vector<int8_t > G_mu; vector<uint32_t > G_Qpref; vector<ll> G_Dpref; vector<int > G_Mertens; unordered_map<ll, ll> G_cacheD; unordered_map<ll, ll> G_cacheQ; 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; } void build_sieve (int limit) { vector<uint32_t > lp (limit + 1 , 0 ) ; vector<uint16_t > tau (limit + 1 , 0 ) ; vector<uint8_t > exp (limit + 1 , 0 ) ; 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) { G_mu[(int )x] = 0 ; uint8_t e = (uint8_t )(ei + 1 ); exp[(int )x] = e; 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 ); } } } 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; sD += tau[i]; G_Qpref[i] = sQ; G_Dpref[i] = sD; } } 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; } } 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; 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; } 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; 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 += v * ((ll)G_Mertens[(int )hi] - (ll)G_Mertens[(int )(lo - 1 )]); } G_cacheQ.emplace (x, sum); return sum; } 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 ); ll S = 0 ; ll k = 1 ; ll prevQ = 0 ; 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 ; }