总结题意
求某个范围内所有数对的最大公约数的和.
那么先考虑如何求某个范围内互质的数有几对.
已知某个范围内数对的个数: n*m.
已知某个范围内有公约数 x 的数对的个数: ⌊n/x⌋∗⌊m/x⌋.(⌊x⌋表示x向下取整)
怎么求呢?
小学生容斥
公约数为 1 的数对个数
减去公约数为 2 的
减去公约数为 3 的
加上公约数既有 2 又有 3 的(其实就是有 6 )的
减去公约数为7的
加减的性质
设上边那个”公约数”是x.
被”加”进答案里的 x 有偶数个质因数.
被”减”进答案里的 x 有奇数个质因数.(原因? 容斥啦!)
而且所有的 x 都不是任何数的平方.(想一想为什么, 下面有答案)
于是可以线性筛!
莫比乌斯函数
设µ(x) = 1/0/ − 1(上面的系数, 加或减)
于是互质的数对的个数就变成了
(说到这里听不懂我在说什么的就再看一遍或者百度一下莫比乌斯函数.)
而n/i的可能值只有O( √ n)个(证明?懒得证)
证明:
分类讨论 i < √ n 和 i > √ n.
这两种情况相互对应,具体证明(略)
而 μ 函数可以用前缀和的方法快速求某一段的和.
后面两个取整可以逐段合并.
于是有了一个 O(n ∗√ n) 的算法.
得到了 80 分!
激烈鼓掌.
优化
看外层的循环!
也是从 1 到 n 诶.
也是计算 ⌊ n/i ⌋有关的东西诶.
继续合并! 就到了 O( √ n).
于是总复杂度就变成了O( √ n ∗√ n) = O(n).
可喜可贺!
代码
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long dint;
#define _l (long long int)
const int mod = 998244353;
const int maxn = 10000003;
int isp[maxn], pn[maxn], tp, mu[maxn], smu[maxn];
void pre(int n) {
memset(isp, 0, sizeof(isp));
tp = 0;
smu[0] = 0;
smu[1] = mu[1] = 1;
for (int i = 2; i <= n; ++ i) {
if (!isp[i]) {
mu[pn[tp ++] = i] = -1;
}
for (int j = 0; j < tp && i * pn[j] <= n; ++ j) {
isp[i * pn[j]] = 1;
if (i % pn[j]) {
mu[i * pn[j]] = -mu[i];
} else {
mu[i * pn[j]] = 0;
break;
}
}
smu[i] = smu[i - 1] + mu[i];
}
}
int calc(int n, int m) {
int s = 0;
for (int i = 1, j; i <= n; i = j + 1) {
j = min(n / (n / i), m / (m / i));
s = (s + _l (m / i) * (n / i)% mod * (smu[j] - smu[i - 1]))% mod;
if (s < 0) {
s += mod;
}
}
return s;
}
inline int sumSeg(int a, int b) {
return _l (a + b) * (b - a + 1) / 2 % mod;
}
int main() {
int n, m, s = 0;
scanf("%d%d", &n, &m);
if (n > m) {
swap(n, m);
}
pre(n);
for (int i = 1, j; i <= n; i = j + 1) {
j = min(n / (n / i), m / (m / i));
s = (s + _l calc(n / i, m / i) * sumSeg(i, j))% mod;
if (s < 0) {
s += mod;
}
}
printf("%d\n", s);
}