SP5971 LCMSUM - LCM Sum
知识点: 莫比乌斯反演
原题面:Luogu
扯
写公式好花时间啊。
题意简述
\(T\) 次询问,每次询问给定 \(n\),求:
\[\sum_{i=1}^{n}\operatorname{lcm}(i,n) \]\(1<T\le 3\times 10^5\),\(1\le n\le 10^6\)。
时限 527ms。
分析题意
法一:无脑暴力
先拆 \(\operatorname{lcm}\),原式等价于:
套路的枚举 \(\gcd(i,n)\),调换枚举顺序,原式等价于:
把 \(i,n\) 中的 \(d\) 提出来,变为枚举 \(\frac{i}{d}\),消去整除的条件,原式等价于:
代入 \([\gcd(i,j) = 1] = \sum\limits_{d\mid \gcd(i,j)} {\mu (d)}\),原式等价于:
值得注意的是 \(t\) 的上界为 \(\frac{n}{d}\),\(dt\le n\)。
调换枚举顺序,先枚举 \(t\),原式等价于:
套路地消去整除的条件,把 \(i\) 中的 \(t\) 提出来,原式等价于:
对于最后的一个求和项,设 \(g(x) = \sum\limits_{i=1}^{x}i = \frac{x(x+1)}{2}\),显然可以 \(O(1)\) 求解,原式等价于:
考虑枚举 \(T = dt\),显然 \(T\le n\)。
\(\mu(t)t\) 与 \(d\) 无关,可以直接考虑枚举 \(t|T\),原式等价于:
前半块是一个数论分块的形式,可以 \(O(\sqrt{n})\) 求解。
考虑后半块,设 \(f(n)=\sum\limits_{d|n}\mu(d)d\),发现它是一个积性函数,可以线性筛筛出,具体地:
其中 \(p\) 为 \(n\) 的最小质因子。
此时已经可以线性筛 + 数论分块求解,复杂度 \(O(n+T\sqrt{n})\),比较菜鸡,时限 500ms 过不了。
考虑筛出 \(f\) 后再用埃氏筛预处理 \(\sum\limits_{T=1}^{n}g(\left\lfloor\dfrac{n}{T}\right\rfloor)f(T)\),输出时乘上 \(n\),复杂度变为 \(O(n\log^2 n + n)\)。
法二:
同样先拆 \(\operatorname{lcm}\),枚举 \(\gcd(i,n)\),调换枚举顺序,原式等价于:
把 \(i,n\) 中的 \(d\) 提出来,变为枚举 \(\frac{i}{d}\),消去整除的条件,原式等价于:
调整枚举对象,上式等价于:
考虑 \(\sum\limits_{i=1}^{d}[\gcd(i,d) = 1]i\) 的实际意义,表示 \([1,d]\) 中与 \(d\) 互质的数的和。
当 \(d>1\) 时,与 \(d\) 互质的数总是成对存在,即若 \(\gcd(i,d)=1\) 成立,则 \(\gcd(d-i,d)=1\) 成立。
每对这样的数的和为 \(d\),共有 \(\frac{\varphi(d)}{2}\) 对这样的数。
则原式等价于:
可以直接预处理答案。
预处理时先线性筛出 \(\varphi\),再埃氏筛枚举 \(i\) 的倍数,令它们的答案加上 \(\frac{\varphi(i)i}{2}\),最后输出时乘上 \(n\)。
复杂度 \(O(n\log^2 n + T)\)。
爆零小技巧
注意线性筛时的初始化。
代码实现
法二代码
//知识点:莫比乌斯反演
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define ll long long
const int kMaxn = 1e6;
//=============================================================
ll phi[kMaxn + 10], ans[kMaxn + 10];
int pnum, p[kMaxn + 10];
bool flag[kMaxn + 10];
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void GetMax(int &fir, int sec) {
if (sec > fir) fir = sec;
}
void GetMin(int &fir, int sec) {
if (sec < fir) fir = sec;
}
void GetPrime() {
phi[1] = 1, flag[1] = true; //注意初始化
for (int i = 2; i <= kMaxn; ++ i) {
if (! flag[i]) {
p[++ pnum] = i;
phi[i] = i - 1ll;
}
for (int j = 1; j <= pnum && i * p[j] <= kMaxn; ++ j) {
flag[i * p[j]] = true;
if (i % p[j]) {
phi[i * p[j]] = phi[i] * phi[p[j]];
} else {
phi[i * p[j]] = phi[i] * p[j];
break;
}
}
}
for (int i = 1; i <= kMaxn; ++ i) {
for (int j = 1; i * j <= kMaxn; ++ j) {
ans[i * j] += (i == 1 ? 1 : 1ll * phi[i] * i / 2);
}
}
}
//=============================================================
int main() {
GetPrime();
int T = read();
while (T --) {
int n = read();
printf("%lld\n", 1ll * ans[n] * n);
}
return 0;
}