题目背景
出题是一件痛苦的事情!
相同的题目看多了也会有审美疲劳,于是我舍弃了大家所熟悉的 A+B Problem,改用 A-B 了哈哈!
题目描述
给出一串正整数数列以及一个正整数 C,要求计算出所有满足 A−B=C 的数对的个数(不同位置的数字一样的数对算不同的数对)。
输入格式
输入共两行。
第一行,两个正整数 N,C。
第二行,N 个正整数,作为要求处理的那串数。
输出格式
一行,表示该串正整数中包含的满足 A−B=C 的数对的个数。
输入输出样例
输入
4 1 1 1 2 3
输出
3
说明/提示
对于 75%的数据,1≤N≤2000。
对于 100% 的数据,1≤N≤2×10^5,0≤ai<2^30,1≤C<2^30。
2017/4/29 新添数据两组
思路
(我)数据结构
1.用vector保存数组。
2.用set保存,自动排序和去重。
3.用map记录各元素出现次数。(map<int,int> mp)
4.指针it便利set内每一个元素,result+=mp[*it]*mp[*it+c]。(*it+c不存在即mp[*it+c]==0,所以不用专门去判断set内是否存在*it+c)
优点:操作很无脑,库函数会用就行。
缺点:时间和空间复杂度明显过高。(虽然这道题能AC掉)
#include<iostream>
#include<vector>
#include<set>
#include<map>
using namespace std;
//qxt_version
int main(){
long long int c,result;
int n;
result=0;
cin>>n>>c;
vector<long long int> vec(n,0);
for(int i=0;i<n;i++){ //用vector保存数组
cin>>vec[i];
}
set<long long int> st(vec.begin(),vec.end()); //用vector初始化set
map<long long int,long long int> mp; //创建map
for(int i=0;i<n;i++){
mp[vec[i]]++; //遍历vector,保存元素及重复次数
}
for(auto it=st.begin();it!=st.end();++it){
result+=mp[(*it)+c]*mp[(*it)]; //加组合数
}
cout<<result<<endl;
return 0;
}
(trm)二分查找(膜拜)
1.预先开一个2e5+5的数组nums,保存输入数组。
2.遍历数组,已知c和nums[i],用二分查找去找元素为c+nums[i]的范围的上下界l和r(左开右开区间(l,r))
3.(l,r)长度r-l-1即result增加值。
优点:节约时间和空间
缺点:二分查找上下界难写
#include<iostream>
#include<algorithm>
int nums[200005];
int down(int x,int n){ //找到值为x的元素相邻左边元素的下标
int l,r;
l=0;r=n+1; //左闭右开区间(找左下标)
while(l+1<r){
int mid=(l+r)/2;
if(nums[mid]>=x) r=mid; //保证闭区间闭的边界为左边相邻元素的下标
else l=mid;
}
return l;
}
int up(int x, int n){ //找到值为x的元素相邻右边元素的下标
int l,r;
l=0;r=n+1;
while(l+1<r){ //左开右闭区间(找右下标)
int mid=(l+r)/2;
if(nums[mid]>x) r=mid; //保证闭区间闭的边界为右边相邻元素的下标
else l=mid;
}
return r;
}
int main(){
int n,c;
std::cin>>n>>c;
long long int ans=0;
for(int i=1;i<=n;i++) std::cin>>nums[i];
nums[n+1]=0x3f3f3f3f; //数组[1,n],涉及开区间,要初始化[n+1]为inf
std::sort(nums+1,nums+n+1); //对[1,n+1]排序
for(int i=1;i<=n;i++){
int x=c+nums[i];
int l=down(x,n);
int r=up(x,n);
ans+=r-l-1;
}
std::cout<<ans;
return 0;
}
个人疑惑
1.为什么初始化nums[0]=0而nums[n+1]=inf?
sort的范围[1,n+1],n+1的值非读入,取inf最大值占位,保证必在最右侧。
2.为什么down和up函数内初始化l=0,r=n+1?
因为我们找的是左/右侧相邻元素下标,数组范围[1,n],左侧为0,右侧为n+1
心得
奥义·区间维护
在不开编译器的情况下手搓二分,一次成功的关键在于有意识维护区间。
1.对于左闭右闭区间
对于[0,n-1]:
while判断条件:
找到了:l==r,所以l<r是不断循环的前提,当l==r时就会跳出这个循环。在while外判断该元素是否为target
l和r的更新:
对于nums[mid]>=target,在[mid,r]内没有排除==的情况,所以r=mid而不是r=mid-1
对于nums[mid]<target,则在[l,mid]内排除了==的情况,所以l=mid+1而不是l=mid
判断是否找到target:
nums[l](或nums[r])==target?true:false
//左闭右闭
bool binary_search(int n, int target){
int l=0,r=n-1;
while(l<r){
int mid=(l+r)>>1;
if(nums[mid]>=target){
r=mid-1;
} else {
l=mid;
}
}
return nums[l]==target?true:false;
}
2.对于左闭右开区间
对于[0,n):
while判断条件:
找到了:l+1==r,所以l+1<r是不断循环的前提。
l和r的更新:
对于nums[mid]>target,[mid,r]内排除了==,因为右侧为开区间,r=mid
对于nums[mid]<=target,[l,mid]内没排除==,左侧为闭区间,l=mid
判断是否找到target:
nums[l](或nums[r-1])==target?true:false
//左闭右开
bool binary_search(int n, int target){
int l=0,r=n;
while(l+1<r){
int mid=(l+r)>>1;
if(nums[mid]>target){
r=mid;
} else {
l=mid;
}
}
return nums[l]==target?true:false;
}
为什么不把nums[mid]==target的情况直接写在while里呢(?)trm说是在数学上好证,没有破坏完整性。。有点听不太懂哈哈。。