[SDOI2015]约数个数和 题解

[SDOI2015]约数个数和 题解

题目地址:洛谷:【P3327】[SDOI2015]约数个数和 – 洛谷、BZOJ:Problem 3994. — [SDOI2015]约数个数和

题目描述

设d(x)为x的约数个数,给定N、M,求\sum^N_{i=1}\sum^M_{j=1} d(ij)

输入输出格式

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

输出格式:
T行,每行一个整数,表示你所求的答案。

输入输出样例

输入样例#1:

2
7 4
5 6

输出样例#1:

110
121

说明

1<=N, M<=50000
1<=T<=50000

题解

参考资料:BZOJ 3994 – [SDOI2015]约数个数和 | Sengxian’s Blog
首先我们要知道一个事,d(xy) = \sum_{i|x} \sum_{j|y} [\mathrm{gcd}(i, j) == 1]。有了它,我们就可以对要求的式子进行变形,首先是最开始的版本([表达式]表示表达式为真时值为1,否则值为0)
\sum^n_{i=1} \sum^m_{j=1} d(ij)
代换掉d函数
\sum^n_{i=1} \sum^m_{j=1} \sum_{a|i} \sum_{b|j} [\mathrm{gcd}(a, b) == 1]
我们知道有\sum_{x|n} \mu(x) = [n == 1],因此可以带进上面的条件
\sum^n_{i=1} \sum^m_{j=1} \sum_{a|i} \sum_{b|j} \sum_{x|\mathrm{gcd}(a, b)} \mu(x)
考虑枚举x统计每个\mu(x)的数量,上式可变换为
\sum^n_{i=1} \sum^m_{j=1} \sum_{x|i, x|j} \mu(x) d(\frac{i}{x}) d(\frac{j}{x})
\mu(x)项放在前面
\sum^n_{i=1} \mu(x) \sum_{x|i} d(\frac{i}{x}) \sum_{x|j} d(\frac{j}{x})
后面两项换个元
\sum^n_{i=1} \mu(x) \sum_{i=1}^{\lfloor \frac{n}{x} \rfloor} d(i) \sum_{i=1}^{\lfloor \frac{m}{x} \rfloor} d(j)
我们如果能预处理出d(x), \mu(x),再用一波除法分块,就可以把原来O(n^2)的暴力求值变成现在的O(n \sqrt{n})优秀算法。而d(x)是一个积性函数,可以跟\mu(x)一起筛出来。
我们考虑如何筛d(x)。两两互质的情况可以直接乘起来,当遇到不互质的情况,prime[j]一定是i中最小的那个质因子,我们可以求出每个数最小质因子的次数,从而获取其他质因子的选择方案数之乘积,推得i*prime[j]这个数字的d值。这是由于如果能够将x表示为p_1^{c_1}p_2^{c_2} \cdots p_k^{c_k}的形式,则d(x) = \prod_{i=1}^n (c_i + 1)

代码

// Code by KSkun, 2018/6
#include <cstdio>
#include <cctype>  

#include <algorithm>

typedef long long LL;

inline char fgc() {
    static char buf[100000], *p1 = buf, *p2 = buf;
    return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) 
        ? EOF : *p1++;
}

inline LL readint() {
    register LL res = 0, neg = 1;
    register char c = fgc();
    while(!isdigit(c)) {
        if(c == '-') neg = -1;
        c = fgc();
    }
    while(isdigit(c)) {
        res = (res << 1) + (res << 3) + c - '0';
        c = fgc();
    }
    return res * neg;
}

const int MAXN = 50005;

bool notprime[MAXN];
int prime[MAXN], ptot, mu[MAXN], d[MAXN], cnt[MAXN];

inline void sieve() {
    mu[1] = d[1] = 1; notprime[1] = true;
    for(int i = 2; i < MAXN; i++) {
        if(!notprime[i]) {
            prime[++ptot] = i; mu[i] = -1; d[i] = 2; cnt[i] = 1;
        }
        for(int j = 1; j <= ptot && i * prime[j] < MAXN; j++) {
            int t = i * prime[j];
            notprime[t] = true;
            if(i % prime[j]) {
                mu[t] = -mu[i]; d[t] = d[i] * 2; cnt[t] = 1;
            } else {
                mu[t] = 0; cnt[t] = cnt[i] + 1; d[t] = d[i] / (cnt[i] + 1) * (cnt[t] + 1);
            }
        }
    }
    for(int i = 2; i < MAXN; i++) {
        mu[i] += mu[i - 1];
        d[i] += d[i - 1];
    }
}

inline LL cal(int n, int m) {
    if(n > m) std::swap(n, m);
    LL res = 0;
    for(int i = 1; i <= n;) {
        int j = std::min(n / (n / i), m / (m / i));
        res += 1ll * (mu[j] - mu[i - 1]) * d[n / i] * d[m / i];
        i = j + 1;
    }
    return res;
}

int T, n, m;

int main() {
    sieve();
    T = readint();
    while(T--) {
        n = readint(); m = readint();
        printf("%lld\n", cal(n, m));
    }
    return 0;
}


发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据