A-B数对(题解)

A-B 数对

题目背景

出题是一件痛苦的事情!

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

题目描述

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

输入格式

输入共两行。

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

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

输出格式

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

样例 #1

样例输入 #1

4 1
1 1 2 3

样例输出 #1

3

提示

对于 75 % 75\% 75% 的数据, 1 ≤ N ≤ 2000 1 \leq N \leq 2000 1N2000

对于 100 % 100\% 100% 的数据, 1 ≤ N ≤ 2 × 1 0 5 1 \leq N \leq 2 \times 10^5 1N2×105 0 ≤ a i < 2 30 0 \leq a_i <2^{30} 0ai<230 1 ≤ C < 2 30 1 \leq C < 2^{30} 1C<230


第一种方法:

  • 分析
    如果决定枚举 A A A,那么问题就变成了统计数列中 B + C B+C B+C出现了多少次。把数列排序,那么 B + C B+C B+C会对应这个数列的连续一段。只要能快速找到这个连续端的左端点和右端点。也就是 B + C B+C B+C在有序数列中第一次出现和最后一次出现的位置,这道题目就可以迎刃而解。
数组01234567
原输入73433374
排序后33334477
N = 8 N=8 N=8时的情况

仔细一看,现在的问题已经归纳成了一道二分查找题。这里介绍一下 S T L STL STL 中的

lower_bound()upper_bound()//需要用到include<algorithm>

用法如下:

lower_bound(begin,end,val)在值有序的数组连续地址[begin,end)中找到第一个位置并返回其地址使得val插
入在这个位置前面,整个数组仍然保持有序
upper_bound(begin,end,val)在值有序的数组连续地址[begin,end)中找到最后一个位置并返回其地址使得val
插入在这个位置前面,整个数组仍然保持有序

假如排序后的数组名为 a a a。如果对“地址”不是很了解,也可以认为其返回值减去数组名 a a a(其实等于 a [ 0 ] a[0] a[0])刚好等于所要找的元素的数组下标。例如:

lower_bound(a,a+n,3)-a=0,lower_bound(a,a+n,7)-a=6,
upper_bound(a,a+n,3)-a=4,upper_bound(a,a+n,7)-a=8

既然lower_bound能找到某数第一次出现的位置,upper_bound能找到某数最后一次出现的位置(的后面),那么这个数出现的次数就可以表示为upper_bound(···)-lower_bound(···),基于这个优雅的表达,给出如下代码:

#include<cstdio>
#include<algorithm>
#define maxn 200010
using namespace std;
typedef long long ll;
ll a[maxn];
int n,c;
int main(){
	ios::sync_with_stdi(false);
	cin.tie(0);
	for(int i=0;i<n;i++){
		cin>>a[i];
	}sort(a,a+n);
	ll tot=0;
	for(int i=0;i<n;i++){
		tot+=upper_bound(a,a+n,a[i]+c)-lower_bound(a,a+n,a[i]+c);
	}
	cout<<tot;
	return 0;
}

第二种方法

当然,这道题还有一种(排序后) O ( n ) O(n) O(n)的做法:同样是寻找lower_bound和upper_bound的位置,可以发现随着被查询的 a [ i ] + c a[i]+c a[i]+c的增大,lower_bound和upper_bound的位置也在变后,那么可以把这两个位置维护出来,即随着 a [ i ] + c a[i]+c a[i]+c的增大而向后移动。因为这两个指针的移动次数不超过 n n n,所以这个算法是 O ( n ) O(n) O(n)的,代码如下:

#include<cstdio>
#include<algorithm>
#define maxn 200010
using namespace std;
typedef long long ll;
ll a[maxn];
int n,c;
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	
	for(int i=0;i<n;i++){
		cin>>a[i];
	}sort(a,a+n);
	ll tot=0;
	for(int i=0,l=0,r=0;i<n;i++){
		while(l<n&&a[l]<a[i]+c)l++;//l相当于lower_bound,第一个a[l]>=a[i] +c的位置上
		while(r<n&&a[r]<=a[i]+c)r++;//r相当于upper_bound,第一个a[r]>a[i] +c的位置上
		tot+=r-l;
	}
	cout<<tot;
	return 0;
} 

第三种方法

举个例子,假设 C = 3 , C=3, C=3,数列是 2 , 5 , 3 , 7 , 5 , 2 , 4 , 8 2,5,3,7,5,2,4,8 2,5,3,7,5,2,4,8。将数列排序得到 2 , 2 , 3 , 4 , 5 , 5 2,2,3,4,5,5 2,2,3,4,5,5。对于序列中的一个数 s [ i ] s[i] s[i],想要快速找到 s [ i ] − C s[i]-C s[i]C。如果 s [ i ] − C s[i]-C s[i]C 。如果 s [ i ] − C s[i]-C s[i]C这个数字存在的话,一定在排序后的数列中是连续。维护两个指针,左指针 l l l和右指针 r r r使得 s [ l ] s[l] s[l]是首个大于等于 s [ i ] − C s[i]-C s[i]C的数, s [ r ] s[r] s[r]是首个大于 s [ i ] − C s[i]-C s[i]C的数。这样,从 s [ l ] s[l] s[l] s [ r − 1 ] s[r-1] s[r1]都是等于 s [ i ] − C s[i]-C s[i]C的数字,即等于 s [ i ] − C s[i]-C s[i]C的数字个数有 r − 1 r-1 r1个,累加进答案中。 s [ i ] − C s[i]-C s[i]C是越来越大,所以 l l l r r r都会越来越往右

i i i01234567
排序后的 s [ i ] s[i] s[i]22345578 l = r = 0 l=r=0 l=r=0
s [ 0 ] s[0] s[0]
2
2345578 l = r = 0 l=r=0 l=r=0
s [ 1 ] s[1] s[1]
2
2
345578 l = r = 0 l=r=0 l=r=0
s [ 2 ] s[2] s[2]
2
2
3
45578 l = r = 0 l=r=0 l=r=0
s [ 3 ] s[3] s[3]
2
23
4
5578 l = 0 ; r = 2 ; l=0;r=2; l=0;r=2;答案增加 r − l = 2 r-l=2 rl=2
s [ 4 ] s[4] s[4]
2
2
3
4
5
578 l = 0 ; r = 2 ; l=0;r=2; l=0;r=2;答案增加 r − l = 2 r-l=2 rl=2
s [ 5 ] s[5] s[5]
2
2
3
45
5
78 l = 0 ; r = 2 ; l=0;r=2; l=0;r=2;答案增加 r − l = 2 r-l=2 rl=2
s [ 6 ] s[6] s[6]223
4
5
5
7
8 l = 3 ; r = 4 ; l=3;r=4; l=3;r=4;答案增加 r − l = 1 r-l=1 rl=1
s [ 7 ] s[7] s[7]2234
5
5
7
8
l = 4 ; r = 6 ; l=4;r=6; l=4;r=6;答案增加 r − l = 2 r-l=2 rl=2
#include<bits/stdc++.h>
using namespace std;
#define maxn 200010
#define ll long long
int s[maxn];
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	int n,c;
	cin>>n>>c;
	for(int i=0;i<n;i++)cin>>s[i];
	sort(s,s+n);
	int l=0,r=0;
	ll sum=0;
	for(int i=0;i<n;i++){
		while(s[l]<s[i]-c&&l<n)l++;
		while(s[r]<=s[i]-c&&r<n)r++;
		if(s[i]-s[l]==c){
			sum+=r-l;
		}
	}
	cout<<sum;
	return 0;
} 
  • 33
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值