题解:洛谷 P1102 A-B数对

【题目来源】

洛谷:P1102 A-B 数对 - 洛谷

【题目描述】

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

【输入】

输入共两行。

第一行,两个正整数 \(N,C\)

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

【输出】

一行,表示该串正整数中包含的满足 \(A−B=C\) 的数对的个数。

【输入样例】

4 1
1 1 2 3

【输出样例】

3

【核心思想】

  1. 问题分析:给定长度为 \(N\) 的数组和一个整数 \(C\),需要统计所有满足 \(A - B = C\) 的有序数对 \((A,B)\) 的个数,其中 \(A\)\(B\) 取自数组中的元素,位置不同视为不同。等价于统计每个元素 \(x\) 作为 \(B\) 时,\(x+C\) 作为 \(A\) 出现的次数,或每个元素 \(x\) 作为 \(A\) 时,\(x-C\) 作为 \(B\) 出现的次数。

  2. 算法选择

    • 排序 + 双指针:先对数组排序,利用单调性,对每个 \(B\)(位置 \(k\)),用两个指针分别找到第一个使 \(A - B \ge C\) 的位置 \(i\) 和第一个使 \(A - B > C\) 的位置 \(j\),则 \(j - i\) 就是等于 \(C\)\(A\) 的个数。累加所有 \(k\) 的贡献。
    • 哈希表计数(map/unordered_map):统计每个数字出现次数,遍历每个数字 \(x\),若 \(x+C\) 存在,则 \(ans += cnt[x] \times cnt[x+C]\)
    • 二分查找:排序后,对每个 \(B\),用二分查找找到值等于 \(B+C\) 的左右边界,累加个数。
    • 双指针法时间复杂度 \(O(N \log N)\),空间 \(O(1)\)(排序外),适合 \(N\) 较大。
  3. 关键步骤

    • 读入 \(N, C\) 和数组 \(a\)
    • 对数组 \(a\) 进行升序排序。
    • 初始化 \(ans = 0\)
    • 使用三个指针 \(i, j, k\),其中 \(k\) 遍历数组作为 \(B\)
      • 保持 \(i\) 为第一个满足 \(a[i] - a[k] \ge C\) 的索引(即 \(a[i] \ge a[k] + C\))。
      • 保持 \(j\) 为第一个满足 \(a[j] - a[k] > C\) 的索引(即 \(a[j] > a[k] + C\))。
      • 则满足 \(a[x] - a[k] == C\)\(x\) 的范围是 \([i, j-1]\),个数为 \(j - i\)
      • 累加 \(ans += j - i\)
    • 输出 \(ans\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N \log N)\)(排序)+ \(O(N)\)(双指针),总体 \(O(N \log N)\)
    • 空间复杂度:\(O(1)\)(不考虑排序的栈空间)或 \(O(N)\)(存储数组)。
  5. 双指针的核心思想

    • 数组排序后,对固定的 \(B\),满足条件的 \(A\)\(a[k]+C\) 在有序数组中形成一个连续区间。由于 \(k\) 递增时 \(B\) 增大,\(A\) 的候选区间也向右移动,因此可以用两个指针 \(i\)\(j\) 分别维护区间的左右边界,每次仅需移动指针,无需重新查找。
    • 双指针法利用了排序后数据的单调性,将 \(O(N \log N)\) 的二分查找优化为 \(O(N)\) 的线性扫描,整体复杂度由排序决定。
    • 适用场景:数组中元素可重复,且需要统计满足差值为固定值的数对个数时,排序加双指针是高效通用的方法。
      【解题思路】

【解题思路】

【算法标签】

普及- #整数二分

【代码详解】

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

// 全局变量:
// n: 数组长度
// c: 目标差值
// a[200005]: 存储输入数组
// sum: 临时存储满足条件的元素个数
// ans: 存储最终结果(满足条件的对数)
int n, c, a[200005], sum;
long long ans = 0;

/**
 * 二分查找左边界(第一个等于q的元素)
 * @param q 要查找的值
 * @return 第一个等于q的元素位置,未找到返回0
 */
int findleft(int q)
{
    int l = 1, r = n + 1;
    while (l < r)
    {
        int mid = l + (r - l) / 2;  // 防止溢出的中间值计算
        if (a[mid] >= q)
        {
            r = mid;
        }
        else
        {
            l = mid + 1;
        }
    }
    if (a[l] == q)
    {
        return l;
    }
    else
    {
        return 0;
    }
}

/**
 * 二分查找右边界(最后一个等于q的元素)
 * @param q 要查找的值
 * @return 最后一个等于q的元素位置,未找到返回0
 */
int findright(int q)
{
    int l = 1, r = n + 1;
    while (l < r)
    {
        int mid = l + (r - l) / 2;
        if (a[mid] <= q)
        {
            l = mid + 1;
        }
        else
        {
            r = mid;
        }
    }
    if (a[l - 1] == q)
    {
        return l - 1;
    }
    else
    {
        return 0;
    }
}

int main()
{
    // 输入数组长度和目标差值
    cin >> n >> c;

    // 输入数组元素
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
    }

    // 对数组进行排序
    sort(a + 1, a + n + 1);

    // 枚举每个元素作为B,查找满足A=B+c的元素
    for (int i = 1; i <= n; i++)
    {
        int q = a[i] + c;  // 计算需要查找的值

        // 查找该值的左右边界
        if (findleft(q) == 0 && findright(q) == 0)
        {
            sum = 0;  // 没有找到满足条件的元素
        }
        else
        {
            sum = findright(q) - findleft(q) + 1;  // 计算满足条件的元素个数
        }

        ans += sum;  // 累加到最终结果
    }

    // 输出满足条件的对数
    cout << ans << endl;

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

#define int long long  // 定义int为long long类型

int n, c, x, s, a, b;  // n: 数字个数, c: 目标差值, x: 临时变量
                       // s: 结果计数器, a,b: 临时存储的键值
map<int, int> m;       // 使用map存储数字及其出现次数

signed main()          // 使用signed替代int,因为定义了int为long long
{
    // 输入数字个数和目标差值
    cin >> n >> c;

    // 输入数字并统计每个数字出现的次数
    for (int i = 0; i < n; i++)
    {
        cin >> x;
        m[x]++;  // 统计数字出现次数
    }

    // 遍历map中的每个元素
    for (map<int,int>::iterator it = m.begin(); it != m.end(); it++)
    {
        a = it->first;   // 当前数字
        b = a + c;       // 计算目标数字

        // 如果目标数字存在
        if (m.count(b))
        {
            // 计算当前数字和目标数字的组合数并累加
            s += m[a] * m[b];
        }
    }

    // 输出满足条件的数字对总数
    cout << s << endl;

    return 0;
}
// 使用双指针再写一遍
#include <bits/stdc++.h>
using namespace std;
int n, c, a[200005];
#define int long long
signed main()
{
    cin >> n >> c;
    for (int i=1; i<=n; i++)
        cin >> a[i];
    sort(a+1, a+n+1);
    int i=1, j=1, ans = 0;
    for (int k=1; k<=n; k++) {
        while (i<=n && a[i]-a[k]<c) i++;
        while (j<=n && a[j]-a[k]<=c) j++;
        ans += j-i;
    }
    cout << ans;
    return 0;
}
// 使用acwing模板二刷
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 200005;
int n, c, a[N], ans;
signed main()
{
    cin >> n >> c;
    for (int i=1; i<=n; i++) cin >> a[i];
    sort(a+1, a+n+1);
    for (int i=1, j=1, k=1; k<=n; k++)  // 左i右j
    {
        while (i<=n && a[i]-a[k]<c) i++;
        while (j<=n && a[j]-a[k]<=c) j++;
        ans += j-i;
    }
    cout << ans;
    return 0;
}

【运行结果】

4 1
1 1 2 3
3
posted @ 2026-09-02 22:18  团爸讲算法  阅读(14)  评论(0)    收藏  举报