K 的倍数或个位是 K

题目链接

题目分析

我们需要计算在区间 [l, r] 内满足以下条件之一的整数的个数:

  1. k 的倍数。
  2. 个位上是 k

由于这两个条件可能有重叠(即一个数既是 k 的倍数,个位又是 k),我们需要使用容斥原理来避免重复计算。

容斥原理

设:

  • A 为区间 [l, r] 内是 k 的倍数的数的集合。
  • B 为区间 [l, r] 内个位是 k 的数的集合。

我们需要计算的是 |A ∪ B|,即 AB 的并集的大小。根据容斥原理:
∣ A ∪ B ∣ = ∣ A ∣ + ∣ B ∣ − ∣ A ∩ B ∣ |A ∪ B| = |A| + |B| - |A ∩ B| AB=A+BAB

其中:

  • |A| 是区间 [l, r] 内是 k 的倍数的数的个数。
  • |B| 是区间 [l, r] 内个位是 k 的数的个数。
  • |A ∩ B| 是区间 [l, r] 内既是 k 的倍数,个位又是 k 的数的个数。

计算方法

  1. 计算 |A|

    • 区间 [l, r] 内是 k 的倍数的数的个数为:
      ∣ A ∣ = ⌊ r k ⌋ − ⌊ l − 1 k ⌋ |A| = \left\lfloor \frac{r}{k} \right\rfloor - \left\lfloor \frac{l-1}{k} \right\rfloor A=krkl1
  2. 计算 |B|

    • 个位是 k 的数可以表示为 10a + k,其中 a 是任意非负整数。
    • 区间 [l, r] 内个位是 k 的数的个数为:
      ∣ B ∣ = ⌊ r − k 10 ⌋ − ⌊ l − 1 − k 10 ⌋ + 1 |B| = \left\lfloor \frac{r - k}{10} \right\rfloor - \left\lfloor \frac{l - 1 - k}{10} \right\rfloor + 1 B=10rk10l1k+1
  3. 计算 |A ∩ B|

    • 既是 k 的倍数,个位又是 k 的数需要满足:
      10 a + k ≡ 0 ( m o d k ) 10a + k \equiv 0 \pmod{k} 10a+k0(modk)
      化简得:
      10 a ≡ − k ( m o d k )    ⟹    10 a ≡ 0 ( m o d k ) 10a \equiv -k \pmod{k} \implies 10a \equiv 0 \pmod{k} 10ak(modk)10a0(modk)
      因为 10a 必须是 k 的倍数,所以 a 必须是 k / \gcd(10, k) 的倍数。
    • d = \gcd(10, k),则符合条件的数为:
      10 ⋅ ( k d ⋅ t ) + k = 10 k d ⋅ t + k 10 \cdot \left( \frac{k}{d} \cdot t \right) + k = \frac{10k}{d} \cdot t + k 10(dkt)+k=d10kt+k
      这是一个等差数列,首项为 k,公差为 10k / d
    • 区间 [l, r] 内符合条件的数的个数为:
      ∣ A ∩ B ∣ = ⌊ r − k 10 k / d ⌋ − ⌊ l − 1 − k 10 k / d ⌋ + 1 |A ∩ B| = \left\lfloor \frac{r - k}{10k / d} \right\rfloor - \left\lfloor \frac{l - 1 - k}{10k / d} \right\rfloor + 1 AB=10k/drk10k/dl1k+1
  4. 最终结果

    • 使用容斥原理计算:
      ∣ A ∪ B ∣ = ∣ A ∣ + ∣ B ∣ − ∣ A ∩ B ∣ |A ∪ B| = |A| + |B| - |A ∩ B| AB=A+BAB

代码实现

以下是 C++ 实现代码:

#include <iostream>
#include <algorithm>
using namespace std;

// 计算最大公约数
int gcd(int a, int b) {
    return b == 0 ? a : gcd(b, a % b);
}

// 计算区间 [l, r] 内是 k 的倍数的数的个数
int countMultiples(int l, int r, int k) {
    return r / k - (l - 1) / k;
}

// 计算区间 [l, r] 内个位是 k 的数的个数
int countLastDigitK(int l, int r, int k) {
    return (r - k) / 10 - (l - 1 - k) / 10 + 1;
}

// 计算区间 [l, r] 内既是 k 的倍数,个位又是 k 的数的个数
int countBoth(int l, int r, int k) {
    int d = gcd(10, k);
    int step = 10 * k / d;
    return (r - k) / step - (l - 1 - k) / step + 1;
}

int main() {
    int n, k;
    cin >> n >> k;

    while (n--) {
        int l, r;
        cin >> l >> r;

        // 计算 |A|, |B|, |A ∩ B|
        int multiples = countMultiples(l, r, k);
        int lastDigitK = countLastDigitK(l, r, k);
        int both = countBoth(l, r, k);

        // 使用容斥原理计算 |A ∪ B|
        int result = multiples + lastDigitK - both;
        cout << result << endl;
    }

    return 0;
}

代码说明

  1. gcd 函数:计算两个数的最大公约数。
  2. countMultiples 函数:计算区间 [l, r] 内是 k 的倍数的数的个数。
  3. countLastDigitK 函数:计算区间 [l, r] 内个位是 k 的数的个数。
  4. countBoth 函数:计算区间 [l, r] 内既是 k 的倍数,个位又是 k 的数的个数。
  5. 主函数:读取输入,调用上述函数计算结果,并输出。

示例运行

输入:

5 3
1 10
11 1000
111 10000
1111 100000
11111 1000000

输出:

3
396
3956
39555
395556

复杂度分析

  • 时间复杂度:每个查询的时间复杂度为 O ( 1 ) O(1) O(1),总复杂度为 O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1),仅使用常数空间。

希望这段代码和解释对你有帮助!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值