[题解] SDOI2015 约数个数和

题目描述

d(x)x 的余数个数,给定 NM,求 i=1Nj=1Md(ij)

输入格式

输入文件包含多组测试数据。第一行,一个整数 T,表示测试数据的组数。接下来的 T 行,每行两个整数 N,M

输出格式

T 行,每行一个整数,表示你所求的答案。

样例输入

2
7 4
5 6
C++

样例输出

110
121
C++

数据范围及约束

T,N,M50 000

解题思路

首先我们要知道 d(ij)=piqj[gcd([p,q])=1],这个在之后给出了证明
i=1nj=1md(ij)=i=1nj=1mpiqj[gcd(p,q)=1]=p=1nq=1mi=1nj=1m[pi][qj][gcd(p,q)=1]=p=1nq=1mnpmq[gcd(p,q)=1]=p=1nq=1mnpmqkpkqμ(k)=k=1min(i,j)μ(k)p=1nq=1mnpmq[kp][kq]=k=1min(i,j)μ(k)p=1n/kq=1m/knpkmqk
可以先预处理出 i=1nni1,2,,50 000 处的答案,然后可以 O(1) 询问后半部分的答案,再套用整除分块,在 O(n) 的时间内做出单组询问

证明
d(ij)=piqj[gcd(p,q)=1]

对于一个 ij 中只含 k 这个质因子,假设 i 中含有 a 个,j 中含有 b 个,那么 i×j 就有 a+b+1 个约数

piqj[gcd(p,q)=1] 中有

  • pk0qk0,k1,k2,,kbb+1
  • pk0,k1,k2,,kaqk0a+1
  • 上面两种情况中重复计算了 pk0 且 ​q 取 ​k0 的情况,减去 ​1

因此对于只含有单一质因子的情况,这个式子是成立的

假设对于含有前 n(n1) 质因子的情况成立,即 i=s=1nksasj=s=1nksbs 已经成立,共有 s=1n(as+bs)+1 个约数,记为 S

考虑假如第 n+1 个质因子,之前已经有 S 个两两互质的二元组 <xi,yi>,第 n+1 个因数 p 中有 a 个,q 中有 b

  • pxi×k0qyi×k0,yi×k1,,yi×kbS×(b+1)
  • pxi×k0,xi×k1,,xi×kaqyi×k0S×(a+1)
  • 上面两种情况重复计算了 pxi×k0qyi×k0 的情况,共 S

总共有 S×(a+b+1) 种,由归纳法证明成立

代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>

using namespace std;
const int MAXN = 50000 + 10;
const int SIZ = 50000;

int pri[MAXN], cnt;
long long sum[MAXN], num[MAXN], mu[MAXN];
bool isNotPrime[MAXN];

inline void init() {
    mu[1] = 1;

    for (register int i=2; i<=SIZ; i++) {
        if (!isNotPrime[i]) {
            mu[i] = -1;
            pri[++cnt] = i;
        }

        for (register int j=1; j<=cnt; j++) {
            int m = i * pri[j];
            if (m > SIZ) break;
            isNotPrime[m] = true;

            if (i % pri[j] == 0) {
                mu[m] = 0;
                break;
            } else {
                mu[m] = -mu[i];
            }
        }
    }

    for (register int i=1; i<=SIZ; i++) {
        sum[i] = sum[i-1] + mu[i];
    }

    for (register int i=1; i<=SIZ; i++) {
        int r = 0;
        long long res = 0;

        for (register int l = 1; l <= i; l = r + 1) {
            r = i / (i / l);
            res += (r - l + 1) * (i / l);
        }

        num[i] = res;
    }
}

inline long long solve(int n, int m) {
    int r = 0;
    long long res = 0;

    for (register int l=1; l <= min(n, m); l = r + 1){
        r = min(n / (n / l), m / (m / l));
        res += (sum[r] - sum[l-1]) * num[n / l] * num[m / l];
    }

    return res;
}

int T, n, m;

int main(){
    init();
    scanf("%d", &T);

    for (register int i=1; i<=T; i++) {
        scanf("%d%d", &n, &m);
        printf("%lld\n", solve(n, m));
    }

    return 0;
}
C++
评论 在线讨论
      加载更多