BZOJ 3994 [SDOI2015]约数个数和 莫比乌斯反演

题目链接

题意

i=1Nj=1Md(ij)
其中 d(x) x 的约数个数

结论

d(ij)=ii|ijj|j[gcd(ii,jj)=1]

证明参见 PoPoQQQ
还有一版 iwtwiioi

推导

=i=1Nj=1Mii|ijj|j[gcd(ii,jj)=1]=ii=1Njj=1M[gcd(ii,jj)=1]i=1Niij=1Mjj=i=1Nj=1MNiMj[gcd(i,j)=1]=d=1min(n,m)μ(d)i=1NdNidi=1MdMjd

接下来的只要处理出
f(k)=i=1kki
即可。
这里
1. 可以直接 分块 O(NN) 预处理.
2. 注意到
i=1kd(i)=i=1kj=1i[j|i]=j=1ki=1kj=j=1kkj

f(k)=ki=1d(i) ,这样就又可以 线性筛 O(N) 解决了.

Code

注释部分为法一分块求 f(k)

#include <bits/stdc++.h>
#define maxn 50000
#define maxm maxn + 10
typedef long long LL;
using namespace std;
bool check[maxm];
int prime[maxm], mu[maxm], cnt[maxn];
LL pre[maxm], sum[maxm], d[maxn];
//void init() {
//    int tot = 0; mu[1] = 1;
//    for (int i = 2; i <= maxn; ++i) {
//        if (!check[i]) {
//            prime[tot++] = i;
//            mu[i] = -1;
//        }
//        for (int j = 0; j < tot; ++j) {
//            if (i * prime[j] > maxn) break;
//            check[i * prime[j]] = true;
//            if (i % prime[j] == 0) {
//                mu[i * prime[j]] = 0;
//                break;
//            }
//            mu[i * prime[j]] = -mu[i];
//        }
//    }
//    for (int i = 1; i <= maxn; ++i) {
//        int lim = maxn / i;
//        for (int j = 0; j <= lim; ++j) {
//            int w = i * j;
//            sum[w] += j;
//            if (w + i <= maxn) sum[w + i] -= j;
//        }
//    }
//    for (int i = 0; i <= 20; ++i) printf("%d ", sum[i]); printf("\n");
//
//    for (int i = 1; i <= maxn; ++i) {
//        pre[i] = pre[i - 1] + mu[i];
//        sum[i] += sum[i - 1];
//    }
//}
void init() {
    int tot = 0; mu[1] = d[1] = 1;
    for (int i = 2; i <= maxn; ++i) {
        if (!check[i]) {
            prime[tot++] = i;
            mu[i] = -1, d[i] = 2, cnt[i] = 1;
        }
        for (int j = 0; j < tot; ++j) {
            if (i * prime[j] > maxn) break;
            check[i * prime[j]] = true;
            if (i % prime[j] == 0) {
                mu[i * prime[j]] = 0;
                cnt[i * prime[j]] = cnt[i] + 1;
                d[i * prime[j]] = d[i] / (cnt[i] + 1) * (cnt[i * prime[j]] + 1);
                break;
            }
            mu[i * prime[j]] = -mu[i];
            cnt[i * prime[j]] = 1;
            d[i * prime[j]] = d[i] << 1;
        }
    }

    for (int i = 1; i <= maxn; ++i) {
        pre[i] = pre[i - 1] + mu[i];
        sum[i] = sum[i - 1] + d[i];
    }
}
void work() {
    int n, m;
    scanf("%d%d", &n, &m);
    int lim = min(n, m), le, ri;
    LL ans = 0;
    for (int i = 1; i <= lim; i = ri + 1) {
        le = i, ri = min(n / (n / i), m / (m / i));
        ans += (pre[ri] - pre[le - 1]) * sum[n / i] * sum[m / i];
    }
    printf("%lld\n", ans);
}
int main() {
    init();
    int T;
    scanf("%d",&T);
    while (T--) work();
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值