文章

【GESP】C++五级练习题 luogu-P1102 A-B 数对

GESP C++ 五级练习题,二分查找考点应用,重点理解二分查找法和双指针法的应用。四-五级考生可以练习。题目难度⭐⭐☆☆☆,洛谷难度等级普及−

luogu-P1102 A-B 数对

题目要求

题目背景

出题是一件痛苦的事情!

相同的题目看多了也会有审美疲劳,于是我舍弃了大家所熟悉的 A+B Problem,改用 A-B 了哈哈!

题目描述

给出一串正整数数列以及一个正整数 $C$,要求计算出所有满足 $A - B = C$ 的数对的个数(不同位置的数字一样的数对算不同的数对)。

输入格式

输入共两行。

第一行,两个正整数 $N,C$。

第二行,$N$ 个正整数,作为要求处理的那串数。

输出格式

一行,表示该串正整数中包含的满足 $A - B = C$ 的数对的个数。

输入输出样例 #1

输入 #1
1
2
3
4 1
1 1 2 3

输出 #1
1
3

说明/提示

对于 $75\%$ 的数据,$1 \leq N \leq 2000$。

对于 $100\%$ 的数据,$1 \leq N \leq 2 \times 10^5$,$0 \leq a_i <2^{30}$,$1 \leq C < 2^{30}$。


题目分析

题目本质是要求对于每一个数 $B$,找到数组中有多少个 $A$ 满足 $A = B + C$。

如果直接使用两层循环枚举 $A$ 和 $B$,在大数据量下($N \leq 2 \times 10^5$)会超时($O(N^2)$)。因此我们需要更高效的方法。 首先,将数组进行排序是所有高效解法的基础,这样可以将无序的查找转化为有序查找。

方法一:二分查找

思路:

  1. 排序数组。
  2. 遍历数组中的每一个数 $a[i]$ 作为 $B$。
  3. 计算出目标值 $target = a[i] + C$。
  4. 在数组中通过二分查找找到等于 $target$ 的起始位置和结束位置。
    • std::lower_bound:找到第一个 $\ge target$ 的位置。
    • std::upper_bound:找到第一个 $> target$ 的位置。
    • 两个迭代器/指针相减,即为等于 $target$ 的元素个数。
  5. 累加所有个数即为答案。 时间复杂度:排序 $O(N \log N)$,每次查找 $O(\log N)$,共 $N$ 次,总复杂度 $O(N \log N)$。

方法二:双指针法

思路:

  1. 排序数组。
  2. 定义两个指针 $L$ 和 $R$,初始都指向数组开头。
  3. 遍历数组中的每一个数 $a[i]$ 作为 $B$,对应的目标是 $target = a[i] + C$。
  4. 利用数组的单调性:随着 $a[i]$ 增大,$target$ 也随之增大(或不变)。因此 $L$ 和 $R$ 只需要向右移动,不需要回退。
    • 移动 $L$ 直到 $a[L] \ge target$。
    • 移动 $R$ 直到 $a[R] > target$。
  5. 此时区间 $[L, R)$ 中的所有元素都等于 $target$,数量为 $R - L$。
  6. 累加答案。 时间复杂度:排序 $O(N \log N)$,双指针线性扫描 $O(N)$,总复杂度 $O(N \log N)$。

注意事项

  • 题目数据范围较大,数字本身和最终统计的答案 ans 都有可能超过 int 上限,务必使用 long long
  • 本题中“不同位置的数字一样的数对算不同的数对”,上述两种方法都正确处理了这一点(通过计算数量而不是判断存在性)。


示例代码

方法一、二分查找

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <algorithm>
#include <iostream>

// 使用 long long 防止溢出,C 的范围是 1 <= C < 2^30,A 和 B 同理
// 定义 long long 的别名 ll,方便编写
typedef long long ll;

// 全局数组,大小稍微开大一点防止越界
// N 最大 2*10^5
ll a[200005];

int main() {
    // 优化 I/O 操作速度
    std::ios::sync_with_stdio(false);
    std::cin.tie(NULL);

    int N;
    ll C;  // C 可能很大,用 ll
    std::cin >> N >> C;

    // 读取输入数组
    for (int i = 0; i < N; i++) {
        std::cin >> a[i];
    }

    // 排序是关键步骤。
    // A - B = C  =>  A = B + C
    // 排序后,对于确定的 B,我们可以二分查找 A (即 B + C)
    std::sort(a, a + N);

    ll ans = 0;
    // 遍历每一个数作为 B
    for (int i = 0; i < N; i++) {
        // 在 B 后面的位置寻找等于 B + C 的数的范围
        // lower_bound 返回第一个 >= (a[i] + C) 的位置
        // 注意:搜索范围 a + i + 1 到 a + N,因为 C >= 1,所以 A > B,A 一定在
        // B后面
        ll* start = std::lower_bound(a + i + 1, a + N, a[i] + C);

        // upper_bound 返回第一个 > (a[i] + C) 的位置
        ll* end = std::upper_bound(a + i + 1, a + N, a[i] + C);

        // 两个指针相减即为中间等于 a[i] + C 的元素个数
        // 如果找不到,start 和 end 会相等,结果为 0
        ans += (end - start);
    }

    // 输出结果
    std::cout << ans << std::endl;

    return 0;
}

方法二、双指针法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <algorithm>
#include <iostream>
#include <vector>

const int MAXN = 200005;
long long a[MAXN];

int main() {
    // 关闭同步,加速 IO
    std::ios::sync_with_stdio(false);
    std::cin.tie(NULL);

    int n;
    long long c;
    std::cin >> n >> c;

    for (int i = 0; i < n; i++) {
        std::cin >> a[i];
    }

    // 1. 排序,为双指针或二分查找做准备
    std::sort(a, a + n);

    long long ans = 0;
    int l = 0, r = 0;

    // 2. 遍历每一个数作为 B (a[i])
    for (int i = 0; i < n; i++) {
        long long target = a[i] + c;  // 我们要找的目标 A

        // 寻找第一个 >= target 的位置 (l)
        // 随着 a[i] 增大,target 也增大,l 只会向右移动,不需要回退
        while (l < n && a[l] < target) {
            l++;
        }

        // 寻找第一个 > target 的位置 (r)
        // 同样,r 也只会向右移动
        while (r < n && a[r] <= target) {
            r++;
        }

        // 区间 [l, r) 中的所有元素都等于 target
        if (l < n && a[l] == target) {
            ans += (r - l);
        }
    }

    std::cout << ans << std::endl;

    return 0;
}

所有代码已上传至Github:https://github.com/lihongzheshuai/yummy-code

GESP 学习专题站:GESP WIKI

luogu-”系列题目可在洛谷题库进行在线评测。

bcqm-”系列题目可在编程启蒙题库进行在线评测。

欢迎加入Java、C++、Python技术交流QQ群(982860385),大佬免费带队,有问必答

欢迎加入C++ GESP/CSP认证学习QQ频道,考试资源总结汇总

欢迎加入C++ GESP/CSP学习交流QQ群(688906745),考试认证学员交流,互帮互助

GESP/CSP 认证学习微信公众号
GESP/CSP 认证学习微信公众号
本文由作者按照 CC BY-NC-SA 4.0 进行授权