You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

7.3 KiB

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

##AcWing 215. 破译密码

一、题目描述

达达正在破解一段密码,他需要回答很多类似的问题: 对于给定的整数 a,bd,有多少正整数对 x,y,满足 x≤ay≤b,并且 gcd(x,y)=d。作为达达的同学,达达希望得到你的帮助。

输入格式 第一行包含一个正整数 n,表示一共有 n 组询问。

接下来 n 行,每行表示一个询问,每行三个正整数,分别为 a,b,d

输出格式 对于每组询问,输出一个正整数,表示满足条件的整数对数。

数据范围 1≤n≤50000,1≤d≤a,b≤50000

输入样例:

2
4 5 2
6 4 3

输出样例:

3
2

提示:gcd(x,y) 返回 xy 的最大公约数。

二、题目分析

前导知识

1、容斥原理 AcWing 890. 能被整除的数

小结 ① 二进制对质数序列的组合式枚举 ② 奇数加,偶数减

2、数论分块 AcWing 199. 余数之和

小结 给出n,k,求 \displaystyle \sum_{i=1}^n k \ \% \ i

核心代码

    ans = n * k;                                    // 看题解的推导公式
    for (l = 1; l <= n; l = r + 1) {                // 枚举左端点,每次跳着走下次的位置就是本次r的位置+1
        if (k / l == 0) break;                      // 1、当k/l=0的时候显然这段以及后面有单调性已经没有贡献了可以 break。
        r = min(k / (k / l), n);                    // 2、注意右端点和n取个min因为>n没有贡献了。
        ans -= (k / l) * (l + r) * (r - l + 1) / 2; // 等差数列求和左到右边界内是公差为1的等差数列首项+末项 乘以 项数 除以2
    }

3、莫比乌斯函数

#### 解题思路 我们分析一下,题目很明了,要求的就是gcd(x,y)=d的个数,转化一下:

\large x'=x/d,y'=y/d
\large |gcd(x,y)=d| \Leftrightarrow  |gcd(x',y')=1|

:上面的"| |",表示的是符合这样条件的(x,y)的数对个数,这两个式子的数对(x,y)(x',y') 是一一对应的。

证明 首先,x , y 的最大公约数是d,那么 除去最大公约数,得到的两个数就 互质。对于每个gcd(x,y)=d都如此操作,那么可以证明,这两个的 数量 是一一对应的。

问题转化为:x≤a/d , y≤b/d gcd(x,y)=1的个数,我们考虑:

补集思想:互质数 = 总数 - 不互质数

总数很容易,a'=a/d,b'=b/d ,两个数的所有组合形式数量就是a'b',下面分析下我们要减去不互质的数:

\large a'b'-⌊\frac{a'}{2}⌋⌊\frac{b'}{2}⌋-⌊\frac{a'}{3}⌋⌊\frac{b'}{3}⌋-...\\
\ \ \ \ \ \ \ +⌊\frac{a'}{6}⌋⌊\frac{b'}{2}⌋+.... \\
\ \ \ \ \ \ \ -.....

这样看来,本题是一个简单的容斥原理!

但是由于容斥原理是O(N)的,询问的次数50000次,会超时,才会想到需要优化。

优化办法

① 莫比乌斯函数

\displaystyle \large a' \cdot b'+\sum_{i=1}^{min(a,b)}⌊\frac{a'}{i}⌋⌊\frac{b'}{i}⌋ \cdot {mobius[i]}

② 数据分块 参考余数之和,yxc没有讲过这道题。

\large \sum_{i=2}^{min(a',b')}⌊\frac{a'}{i}⌋*⌊\frac{b'}{i}⌋*mobius[i]

证明 我们要减去的是什么呢?是不互质的数,不互质数是什么呢?是最大公因数不为1的数,不为1,那可以为几呢?不是1就行废话枚举最大公因数不为1的个数即可,枚举每一个可能的最大公因数,并且计算个数,其中 2 <= i <= min(a',b')

当最大公因数是只有一个质因子的时候,我们可以列出:

\large ⌊\frac{a'}{2}⌋* ⌊\frac{b'}{2}⌋+⌊\frac{a'}{3}⌋*⌊\frac{b'}{3}⌋+.....

最大公因数是2的两对数,实际上就等于2的倍数个数相乘,这两个个数是等同的,同理3也是,但是我们要考虑重复的情况,如6,我们23,都枚举了一次,那么会重复,根据容斥原理,我们要减去,最后我们发现,其实符号就是Mobius函数(这个证明是显然的)。以此类推,枚举到ab中较小的就可以了,因为大于较小的,则式子为0,我们就能推出上面的式子了。

答案呼之欲出:

\large a'*b'+\sum_{i=2}^{min(a',b')}⌊\frac{a'}{i}⌋*⌊\frac{b'}{i}⌋*mobius[i]

我们把这玩意合体一下

\large \sum_{i=1}^{min(a',b')}⌊\frac{a'}{i}⌋*⌊\frac{b'}{i}⌋*mobius[i]
  • \large ⌊\frac{a}{i}⌋×⌊\frac{b}{i}⌋ 对应的序列有很多值都是相同的,计算一次就行(数论分块 优化一下),假设值记为t,对应的区间假设为[l, r],然后让t乘以μ(l)+μ(l+1)+…+μ(r) 即可求出该段中的和,μ(l)+μ(l+1)+…+μ(r),可以使用 莫比乌斯前缀和 求解。

三、实现代码

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;
const int N = 50010;

//线性筛法求莫比乌斯函数(枚举约数)
int mu[N], sum[N]; // 莫比乌斯函数的前缀和
int primes[N], cnt;
bool st[N];
void get_mobius(LL n) {
    mu[1] = 1;
    for (int i = 2; i <= n; i++) {
        if (!st[i]) {
            primes[cnt++] = i;
            mu[i] = -1; //奇数个质因子现在只有1个质因子所以函数值是-1
        }
        for (int j = 0; primes[j] * i <= n; j++) {
            int t = primes[j] * i;
            st[t] = true; //把t筛掉

            // t里有primes[j],而i里如果还有一个primes[j],那么最少有2个及以上的primes[j],根据mobius函数定义此时函数值为0
            if (i % primes[j] == 0) {
                mu[t] = 0;
                break;
            }
            mu[t] = mu[i] * -1;
            //因为执行到这里primes[j]这个质因子只有1个所以整个莫比乌斯函数里有没有某个质因子的个数大于1个取决于i的质因子个数
        } 
    }
    // 维护莫比乌斯函数前缀和
    for (int i = 1; i <= n; i++) sum[i] = sum[i - 1] + mu[i];
}

int main() {
    //筛法求莫比乌斯函数
    get_mobius(N - 1);

    int T;
    cin >> T;

    while (T--) {
        int a, b, d;
        cin >> a >> b >> d;
        //套路啊满满的套路直接先用最大公约数a/gcd(a,b)=a',b/gcd(a,b)=b',映射到a',b'
        a /= d, b /= d;

        // n为 min(a', b')
        int n = min(a, b);

        LL res = 0;

        // l r, 是每一段的左右边界
        // 每次只能取较小的那个上界作为这一段的右端点r
        // 然后下次迭代时下一段的左端点就是r + 1
        for (int l = 1, r; l <= n; l = r + 1) { //分块大法
            r = min(n, min(a / (a / l), b / (b / l)));
            res += (sum[r] - sum[l - 1]) * (LL)(a / l) * (b / l);
        }
        printf("%lld\n", res);
    }
    return 0;
}