题目描述
给出一串正整数数列以及一个正整数 $C$,要求计算出所有满足 $A - B = C$ 的数对的个数(不同位置的数字一样的数对算不同的数对)。
输入格式
输入共两行。
第一行,两个正整数 $N,C$。
第二行,$N$ 个正整数,作为要求处理的那串数。
输出格式
一行,表示该串正整数中包含的满足 $A - B = C$ 的数对的个数。
输入输出样例 #1
输入 #1
输出 #1
说明/提示
对于 $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}$。
题解
二分写法
考虑原问题是要求满足$a_i-a_j=C$的二元组个数,如果我们考虑去枚举$a_i$,那么我们只需要在原序列中找到值为$a_j=a_i-C$的个数即可。
对于一个有序的数组,满足相等值的部分必然构成一段连续的序列,那么我们可以考虑lower_bound()
和upper_bound
函数,可以快速找到这个连续端的左端点和右端点,也就是$a_i-C$在序列中第一次出现和最后一次出现(的后一个)的位置,把这段区间长度加上即可。
由于lower_bound()-a.begin()
和upper_bound()-a.begin()
是各自所求的下标,将其相减就是区间的长度。
代码实现如下:
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
| #include<bits/stdc++.h> using namespace std;
using i64=long long; using u64=unsigned long long; using u32=unsigned; using u128=unsigned __int128;
int main(){ ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
int n,c; cin>>n>>c; vector<int> a(n); for(int i=0;i<n;i++){ cin>>a[i]; } sort(a.begin(),a.end()); i64 ans=0; for(int x:a){ ans+=upper_bound(a.begin(),a.end(),x-c)-lower_bound(a.begin(),a.end(),x-c); } cout<<ans;
return 0; }
|
时间复杂度$O(nlogn)$
哈希写法
和上面的一样,我们遍历$a_i$,则只需要在数组中寻找$a_i-c$的个数。
那么我们可以先使用一个线性表存住序列本身,然后再使用一个map<int,int>
来存住每个数对应的个数,最后依次累加即可。
代码实现如下:
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
| #include<bits/stdc++.h> using namespace std;
using i64=long long; using u64=unsigned long long; using u32=unsigned; using u128=unsigned __int128;
const int inf=0x3f3f3f3f;
int main(){ ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
int n,c; cin>>n>>c; vector<int> a(n); map<int,int> mp; for(int i=0;i<n;i++){ cin>>a[i]; mp[a[i]]++; } i64 ans=0; for(int x:a){ ans+=mp[x-c]; } cout<<ans; return 0; }
|
由于省去了一步排序,时间复杂度$O(n)$,是时间复杂度最优算法,因为读入本身就是$O(n)$了。
双指针写法
运用上面lower_bound()
和upper_bound()
函数的含义,我们可以考虑维护两个指针,左指针$left$和右指针$right$。
对于排序过的数组vector<int> a
而言,遍历数组,令a[left]
是首个≥a[i]-c
的数,令a[right]
是首个>a[i]-c
的数,这样从a[left]
到a[right]
都是等于a[i]-c
的数,则等于a[i]-c
的数的个数永远是right-left
,累加入答案中。
在这个过程中a[i]-c
递增,因此$left$和$right$都会越来越向右。
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
| #include<bits/stdc++.h> using namespace std;
using i64=long long; using u64=unsigned long long; using u32=unsigned; using u128=unsigned __int128;
int main(){ ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
int n; i64 c; cin>>n>>c; vector<i64> a(n); for(int i=0;i<n;i++){ cin>>a[i]; } sort(a.begin(),a.end()); i64 cnt=0; int left=0,right=0; for(int i=0;i<n;i++){ while(a[left]<a[i]-c) left++; while(a[right]<=a[i]-c) right++; if(a[i]-c==a[left]) cnt+=right-left; } cout<<cnt; return 0; }
|
时间复杂度$O(nlogn)$,是sort()
的复杂度。排序后的复杂度是$O(n)$,因为无论是左指针还是右指针都只是从头移动到了尾。