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}\),原式等价于:

\[n\sum_{i=1}^{n}\dfrac{i}{\gcd(i,n)} \]

套路的枚举 \(\gcd(i,n)\),调换枚举顺序,原式等价于:

\[\begin{aligned} &n\sum_{i=1}\sum_{d|i} [\gcd(i,n) = d]\dfrac{i}{d}\\ =& n\sum_{i=1}\sum_{d|i} [\gcd(\dfrac{i}{d},\dfrac{n}{d}) = 1]\dfrac{i}{d}\\ =& n\sum_{d|n}\sum_{i=1}^{n}[d|i][\gcd(\dfrac{i}{d},\dfrac{n}{d}) = 1]\dfrac{i}{d} \end{aligned}\]

\(i,n\) 中的 \(d\) 提出来,变为枚举 \(\frac{i}{d}\),消去整除的条件,原式等价于:

\[n\sum_{d|n}\sum_{i=1}^{\left\lfloor\frac{n}{d}\right\rfloor}[\gcd(i,\dfrac{n}{d}) = 1]i \]

代入 \([\gcd(i,j) = 1] = \sum\limits_{d\mid \gcd(i,j)} {\mu (d)}\),原式等价于:

\[n\sum_{d|n}\sum_{i=1}^{\left\lfloor\frac{n}{d}\right\rfloor} i \sum_{t|\gcd(i,\frac{n}{d})}\mu (t) \]

值得注意的是 \(t\) 的上界为 \(\frac{n}{d}\)\(dt\le n\)
调换枚举顺序,先枚举 \(t\),原式等价于:

\[n\sum_{d|n}\sum_{t|\frac{n}{d}} \mu(t) \sum_{i=1}^{\left\lfloor\frac{n}{d}\right\rfloor} [t|i] i \]

套路地消去整除的条件,把 \(i\) 中的 \(t\) 提出来,原式等价于:

\[n\sum_{d|n}\sum_{t|\frac{n}{d}} \mu(t)t \sum_{i=1}^{\left\lfloor\frac{n}{dt}\right\rfloor} i \]

对于最后的一个求和项,设 \(g(x) = \sum\limits_{i=1}^{x}i = \frac{x(x+1)}{2}\),显然可以 \(O(1)\) 求解,原式等价于:

\[n\sum_{d|n}\sum_{t|\frac{n}{d}} \mu(t)t\cdot g(\left\lfloor\dfrac{n}{dt}\right\rfloor) \]

考虑枚举 \(T = dt\),显然 \(T\le n\)
\(\mu(t)t\)\(d\) 无关,可以直接考虑枚举 \(t|T\),原式等价于:

\[n\sum_{T=1}^{n}g(\left\lfloor\dfrac{n}{T}\right\rfloor)\sum_{t|T}\mu(t)t \]

前半块是一个数论分块的形式,可以 \(O(\sqrt{n})\) 求解。
考虑后半块,设 \(f(n)=\sum\limits_{d|n}\mu(d)d\),发现它是一个积性函数,可以线性筛筛出,具体地:

\[f(n)= \begin{cases} 1-n &n\in \mathrm{primes} \\ f(\frac{x}{p}) &p^2\mid n\\ f(\frac{x}{p})f(p) &p^2\nmid n \end{cases} \]

其中 \(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)\),调换枚举顺序,原式等价于:

\[\begin{aligned} &n\sum_{i=1}^{n}\dfrac{i}{\gcd(i,n)}\\ =& n\sum_{i=1}\sum_{d|i} [\gcd(i,n) = d]\dfrac{i}{d}\\ =& n\sum_{d|n}\sum_{i=1}^{n}[d|i][\gcd({i},{n}) = d]\dfrac{i}{d} \end{aligned}\]

\(i,n\) 中的 \(d\) 提出来,变为枚举 \(\frac{i}{d}\),消去整除的条件,原式等价于:

\[\begin{aligned} &n\sum_{d|n}\sum_{i=1}^{\left\lfloor\frac{n}{d}\right\rfloor}[\gcd(id,n) = d]i\\ =& n\sum_{d|n}\sum_{i=1}^{\left\lfloor\frac{n}{d}\right\rfloor}[\gcd(i,\dfrac{n}{d}) = 1]i \end{aligned}\]

调整枚举对象,上式等价于:

\[n\sum_{d|n}\sum_{i=1}^{d}[\gcd(i,d) = 1]i \]

考虑 \(\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}\) 对这样的数。
则原式等价于:

\[n\sum_{d|n}\dfrac{\varphi(d)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; 
}
posted @ 2020-09-09 11:19  Luckyblock  阅读(174)  评论(0编辑  收藏  举报