题解:洛谷 P1102 A-B数对
【题目来源】
【题目描述】
给出一串正整数数列以及一个正整数 \(C\),要求计算出所有满足 \(A−B=C\) 的数对的个数(不同位置的数字一样的数对算不同的数对)。
【输入】
输入共两行。
第一行,两个正整数 \(N,C\)。
第二行,\(N\) 个正整数,作为要求处理的那串数。
【输出】
一行,表示该串正整数中包含的满足 \(A−B=C\) 的数对的个数。
【输入样例】
4 1
1 1 2 3
【输出样例】
3
【核心思想】
-
问题分析:给定长度为 \(N\) 的数组和一个整数 \(C\),需要统计所有满足 \(A - B = C\) 的有序数对 \((A,B)\) 的个数,其中 \(A\) 和 \(B\) 取自数组中的元素,位置不同视为不同。等价于统计每个元素 \(x\) 作为 \(B\) 时,\(x+C\) 作为 \(A\) 出现的次数,或每个元素 \(x\) 作为 \(A\) 时,\(x-C\) 作为 \(B\) 出现的次数。
-
算法选择:
- 排序 + 双指针:先对数组排序,利用单调性,对每个 \(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\) 较大。
-
关键步骤:
- 读入 \(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\)。
-
时间/空间复杂度:
- 时间复杂度:\(O(N \log N)\)(排序)+ \(O(N)\)(双指针),总体 \(O(N \log N)\)。
- 空间复杂度:\(O(1)\)(不考虑排序的栈空间)或 \(O(N)\)(存储数组)。
-
双指针的核心思想:
- 数组排序后,对固定的 \(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
浙公网安备 33010602011771号