洛谷: P1102 A-B 数对
原题链接
题目背景
出题是一件痛苦的事情!
相同的题目看多了也会有审美疲劳,于是我舍弃了大家所熟悉的 A+B Problem,改用 A-B 了哈哈!
题目描述
给出一串正整数数列以及一个正整数 C C C,要求计算出所有满足 A − B = C A - B = C A−B=C 的数对的个数(不同位置的数字一样的数对算不同的数对)。
输入格式
输入共两行。
第一行,两个正整数 N , C N,C N,C。
第二行, N N N 个正整数,作为要求处理的那串数。
输出格式
一行,表示该串正整数中包含的满足 A − B = C A - B = C A−B=C 的数对的个数。
样例 #1
样例输入 #1
4 1
1 1 2 3
样例输出 #1
3
提示
对于 75 % 75\% 75% 的数据, 1 ≤ N ≤ 2000 1 \leq N \leq 2000 1≤N≤2000。
对于 100 % 100\% 100% 的数据, 1 ≤ N ≤ 2 × 1 0 5 1 \leq N \leq 2 \times 10^5 1≤N≤2×105, 0 ≤ a i < 2 30 0 \leq a_i <2^{30} 0≤ai<230, 1 ≤ C < 2 30 1 \leq C < 2^{30} 1≤C<230。
2017/4/29 新添数据两组
题目解读
给定一个数 C C C, 在 n n n个数中求出所有满足 A − B = C A-B=C A−B=C的 ( A , B ) (A, B) (A,B)数对的个数
算法实现
方法一 双指针法
先用 s o r t sort sort升序排序, 对于每一个 a [ i ] a[i] a[i], 用双指针找到等于 A − C A-C A−C的区间,假设为 a [ l ] 到 a [ r ] ( l < r ) a[l]到a[r](l<r) a[l]到a[r](l<r), 则 a [ l ] 到 a [ r ] a[l]到a[r] a[l]到a[r]的每一个数(包括 a [ l ] 但不包括 a [ r ] a[l]但不包括a[r] a[l]但不包括a[r])都等于 a [ i ] − C a[i]-C a[i]−C, 用 a n s ans ans累加出每一个 a [ i ] a[i] a[i]对应的 a [ l ] 到 a [ r ] a[l]到a[r] a[l]到a[r]的个数即可, 即 a n s + = r − l ans+=r-l ans+=r−l.
至于如何去找到 B = A − C B=A-C B=A−C中的 B B B, 首先将两个指针 l , r l,r l,r都指向第一个数字的下标(本题中是0,如果数组从1开始就是1), 只要 B < A − C B<A-C B<A−C即 s [ l ] < s [ i ] − c s[l]<s[i]-c s[l]<s[i]−c,且满足 l < n l<n l<n的情况下, l l l指针向右移动, l + + l++ l++, 同理我们用类似的方法确定 r r r, 但为了保证r是相同连续区间 B B B的右端点(不包括 r r r),则应该 B ≤ A − C B \le A-C B≤A−C即 s [ r ] ≤ s [ i ] − c s[r] \le s[i]-c s[r]≤s[i]−c.
最后, 找到了相同的连续区间的起始值, 检验一下 s [ l ] = = s [ i ] − c s[l]==s[i]-c s[l]==s[i]−c, 如果成立,则对于 A = s [ i ] A=s[i] A=s[i]来说, 这样的 B B B存在, 用 a n s ans ans累加求和即可; 如果不成立, 则不存在这样的 B B B, 继续找下一个 s [ i ] s[i] s[i]即可, 直到遍历完所有的 s [ i ] s[i] s[i].
方法二 二分法
在排好序的情况下, 函数 u p p e r _ b o u n d ( a + 1 , a + n + 1 , a [ i ] + c ) upper\_bound(a+1,a+n+1,a[i]+c) upper_bound(a+1,a+n+1,a[i]+c)能快速返回第一个大于 a [ i ] + c a[i]+c a[i]+c的地址, l o w e r _ b o u n d ( a + 1 , a + n + 1 , a [ i ] + c ) lower\_bound(a+1,a+n+1,a[i]+c) lower_bound(a+1,a+n+1,a[i]+c)能快速返回第一个大于或者等于 a [ i ] + c a[i]+c a[i]+c的地址, 两个地址相减即可求出有多少个相同的 a [ i ] + c a[i]+c a[i]+c.最后方法同上, 遍历数组累加求和即可.
方法三 STL map
用
m
a
p
map
map容器记录下不同数字出现的次数, 数字的值为
m
a
p
map
map的下标, 数字的个数为
m
a
p
map
map对应下标的值,
再遍历以这
n
n
n个数减去
C
C
C的值为下标的
m
a
p
map
map的值, 并求和, 最后输出这个和即可
AC代码
双指针法一
#include <bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int s[N];
int main(){
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;
long long ans=0;
for(int i=0;i<n;i++){
while( (s[l]<s[i]-c)&&l<n)l++;//找连续相同的B的最左边的那个数的坐标
while( (s[r]<=s[i]-c)&&r<n)r++;//找到连续的B的最右边的那个数的坐标
if(s[i]-c==s[l])ans+=r-l;
}
cout<<ans<<endl;
return 0;
}
双指针法二
#include <bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int a[N],c,n;
int main(){
cin>>n>>c;
long long ans=0;
for(int i=1;i<=n;i++)cin>>a[i];
sort(a+1,a+n+1);//先排序
for(int i=1,j=1,k=1;i<=n;i++){
while(j<=n&&a[j]-a[i]<c)j++;//指针后移,找数字相同的区间
while(k<=n&&a[k]-a[i]<=c)k++;//指针后移,此时[j,k]内的数字是相同的
if(a[j]-a[i]==c&&a[k-1]-a[i]==c&&k-1>=1)ans+=k-j;
}
cout<<ans<<endl;
return 0;
}
二分法
#include <bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int a[N],n,c;
long long s=0;
int main(){
cin>>n>>c;
for(int i=1;i<=n;i++)cin>>a[i];
sort(a+1,a+n+1);
for(int i=1;i<=n;i++)
s+=upper_bound(a+1,a+n+1,a[i]+c)-lower_bound(a+1,a+n+1,a[i]+c);
//s+=upper_bound(a+1,a+n+1,a[i]-c)-lower_bound(a+1,a+n+1,a[i]-c);//等价于上一句
cout<<s<<endl;
return 0;
}
STL map
#include <bits/stdc++.h>
//#include <map> //map的头文件
using namespace std;
const int N=2e5+10;
map<int,int>ds;
int a[N],n,c;
long long ans=0;
int main()
{
cin>>n>>c;
for(int i=1;i<=n;i++){
cin>>a[i];
ds[a[i]]++;
}
for(int i=1;i<=n;i++)
ans+=ds[a[i]-c];
cout<<ans<<endl;
return 0;
}