「LightOJ 1375」LCM Extreme

Description

输入 \(n\),输出下面代码的执行结果:

unsigned long long allPairLcm( int n ) {
    unsigned long long res = 0;
    for( int i = 1; i <= n; i++ )
        for( int j = i + 1; j <= n; j++ )
            res += lcm(i, j); // lcm means least common multiple
    return res;
}

\(T\) 组测试数据。

Hint

  • \(1\le T\le 2\times 10^5\)
  • \(1\le n\le 3\times 10^6\)

Solution

题意:求 \(\sum\limits_{i = 1}^n \sum\limits_{j = i + 1}^n \text{lcm}(i, j) \bmod 2^{64}\) 的值。

直接求显然是 \(O(n^2)\) 的,不能这样做。

首先要知道,原式与 \(\sum\limits_{i = 2}^n \sum\limits_{j = 1}^{i - 1} \text{lcm}(i, j)\) 等价。(下文省略 \(\bmod 2^{64}\))

\(s(x) = \sum\limits_{i = 1}^{x - 1} \text{lcm}(i, x)\) ,那么答案就是 \(\text{ans} = \sum\limits_{i = 2}^n s(i)\)

这相当于暗示着, 只要能在可行的时间内预处理出 \(s(x)\) 的值并做一遍前缀和 ,答案就可以在时间内解出。


\(s(x)\) ——推式子:

\[s(x) = \sum\limits_{i = 1}^{x - 1} \text{lcm}(i, x) = \sum\limits_{i = 1}^{x - 1} \frac{i \times x}{\gcd(i, x)} = x \times \sum\limits_{i = 1}^{x - 1} \dfrac{i}{\gcd(i, x)} \]

以上都应该非常好理解,但最后一个式子看起来非常棘手,使用常规的处理方法貌似无从下手。

对于那个 \(\gcd\) 我们逆向思维一下—— 主动枚举 \(\gcd\) !

\[s(x) = x\times \sum\limits_{g|x} \sum\limits_{i = 1}^{x - 1} \dfrac{i}{g}[\gcd(i, x) = g] \]

其中 \(g\) 是所要枚举的 \(\gcd\) ,而 \(g|x\) 是因为这个最大公约数一定是 \(x\) 的约数。

这个中括号表示,仅当 \(\gcd(i, x) = g\) 成立是,这个对应的 \(\frac{i}{g}\) 的值才能加上去。这相当于是对 \(i\) 值的一个约束。

然而这样的变形貌似效率更低了,我们尝试一下缩小枚举 \(i\) 的范围:

\[s(x) = x \times \sum\limits_{g|x} \sum\limits_{i = 1}^{\frac{x}{g}-1} \dfrac{ig}{g}[\gcd(x, ig) = g] = x\times \sum\limits_{g|x} \sum\limits_{i = 1}^{\frac{x}{g}-1} i[\gcd(\frac{x}{g}, i) = 1] \]

我们发现 \(i\) 的枚举范围从 \([1, n)\) 缩成了 \([1, \frac{n}{g}]\) ——直接让 \(i\) 枚举 \(g\) 的倍数,因为如果不是倍数,那么 \(\gcd(i, x) = g\) 显然不成立。

在最后一个式子里有一个 \(\sum\limits_{i = 1}^{\frac{x}{g}-1} i[\gcd(\frac{x}{g}, i) = 1]\) ,我们可以把它翻译为:小于 \(\frac{x}{g}\) 的,并与 \(\frac{n}{g}\) 互质的数之和。

这样就好办了啊,我们有一个有关于欧拉函数的性质:

小于 \(n\),且与 \(n\) 互质的数之和等于 \(\dfrac{n\times \varphi(n)}{2}\)

把上面这个的东西换到上面的式子中去,易得:

\[s(x) = x \times \large\sum\limits_{g|x} \dfrac{\frac{n}{g} \times \varphi(\frac{n}{g})}{2} \]

我们惊喜地得到了一个简洁且很好计算的式子,这归功于上面一个看似匪夷所思但的确可以解决问题的思路: 逆向枚举 \(\gcd\)


可以开始设计我们的算法了:

  • 筛出 \(\varphi(1) \cdots \varphi(n)\),其中 \(n\) 为其对应数据规模的最大值(即 \(3\times 10^6\)),下同。筛法用 \(O(n\log n)\) 的就够了,常数小可以应付。
  • 预处理 \(s(1)\cdots s(n)\),用枚举一个数的倍数的方法来处理,详见代码,复杂度 \(O(n\log n)\)
  • 直接在 \(s\) 上滚动做前缀和。
  • 对于每个询问,\(O(1)\) 给出答案。

时间复杂度 \(O(n\log n)\) ,空间复杂度 \(O(n)\)

Code

#include <cstdio>

using namespace std;
const int N = 5e6 + 5;

int phi[N];
void initPhi(int n) {
	for (register int i = 1; i <= n; i++)
		phi[i] = i;
	for (register int i = 1; i <= n; i++)
		for (register int j = i << 1; j <= n; j += i)
			phi[j] -= phi[i];
}

unsigned long long s[N];
void initSum(int n) {
	for (register int g = 2; g <= n; g++)
		for (register int x = g; x <= n; x += g)
			s[x] += phi[g] * 1ULL * g / 2 * x;
	for (register int i = 2; i <= n; i++)
		s[i] += s[i - 1];
}

signed main() {
	initPhi(3e6), initSum(3e6);
	int T; scanf("%d", &T);
	for (register int tc = 1; tc <= T; tc++) {
		int n; scanf("%d", &n);
		printf("Case %d: %llu\n", tc, s[n]);
	}
}

猜你喜欢

转载自www.cnblogs.com/-Wallace-/p/12897967.html