0817 T4 者

原题链接

总结题意

求某个范围内所有数对的最大公约数的和.
那么先考虑如何求某个范围内互质的数有几对.
已知某个范围内数对的个数: 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);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值