碰到一道很简单但是做法多样有趣的题,整理一下。
做法一:map容器O(nlogn)思路
将a-b=c问题转化成a=b+c问题
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<map>
using namespace std;
long long n,c,tot2;
int a[200005];
map<int ,int>b;
int main(){
scanf("%d%d",&n,&c);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
b[a[i]]++;
}
for(int i=1;i<=n;i++){
if(b[a[i]+c]) tot2+=b[a[i]+c];
}
cout<<tot2;
return 0;
}
注意,数据比较极限时,如
200000 1
1*100000 2*100000
显然,答案将超过int范围,所以需要用long long
做法二:二分O(nlogn)思路
可以有两种写法,手打,STL
手打的
#include<iostream>
#include<iomanip>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
using namespace std;
int n,c,a[200005];
long long tot;
int check(int t){
int l=1,r=n,mid,total=0;
while(l<=r){
mid=(l+r)/2;
if(a[mid]>=t+c) r=mid-1;
else l=mid+1;
}
int w=l;l=1,r=n;//记录同样数字区间的首位置
while(l<=r){
mid=(l+r)/2;
if(a[mid]>t+c) r=mid-1;
else l=mid+1;
}//找到同样数字区间的下一个位置
return a[w]==a[l-1]? l-w:0;
}
int main(){
scanf("%d%d",&n,&c);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
sort(a+1,a+n+1);
for(int i=1;i<=n;i++)
{
tot+=check(a[i]);
}
cout<<tot;
return 0;
}
STL-upper_bound()&lower_bound()
先决知识
这两个函数是什么?
upper_bound(a,a+n,num);
返回有序数组中第一个a[i]>num的数组下标
lower_bound(a,a+n,num)
返回有序数组中第一个a[i]<=num的数组下标
两个函数都是使用二分来操作
#include<iostream>
#include<iomanip>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
using namespace std;
int n,c,a[200005];
long long tot;
int main(){
scanf("%d%d",&n,&c);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
sort(a+1,a+n+1);
for(int i=1;i<=n;i++)
tot+=upper_bound(a+1,a+n+1,a[i]+c)-lower_bound(a+1,a+n+1,a[i]+c)}
cout<<tot;
return 0;
}
做法三:哈希表O(n)思路
哈希的思路是这样一个思路转化而来的:
用桶装每个数字,只需要枚举b看c是否存在即可,十分类似map思路,但是map是由红黑树维护的映射表,不能达到o(1)的操作效率,为o(logn),但是这样的桶就能达到o(1)。不过很显然这是个空间换时间的做法,会直接MLE。
所以想要使用这种方法,只需要减小空间消耗就可以啦!(哈希:我要上场了
哈希是什么?
正如刚才的问题,假如面对这样一组数据
1 2 3 9999999999
用桶,就要开9999999999的数组,当场炸空间。
但是用哈希,可以达成这种效果
i 1 2 3 4 5 6
a 1 2 5 9 6 5000000
tong 1 2 5 9 6 5000000
tong[] 1 1 1 1 1 1//代表标记了数组对应下标
hash 1 2 3 4 5 6
hash[] 1 2 5 9 6 5000000//hash表中存对应数据,对应%6的情况
哈希真强
我们可以发现,桶的方法浪费了大量的空间,需要一个长为999999999的数组,而哈希只需要一个6长度的数组就可以了
哈希表通过对某一数字取模,重复利用了这个数字内的空间,达到了更高效的利用空间
比如当%5的时候,就重复利用了5以内的空间
1 2 8(%5
1 2 3
哈希冲突
上面的例子可以看出来,面对这组数据就会出现数据冲突
1 2 6(%5
1 2 1
这种情况出现往往是两个原因,数据的密度很大,以及被膜数太小
解决哈希冲突有很多方法,具体见别人的这一篇博客,写的很好https://blog.csdn.net/lvyibin890/article/details/82221820
对于本题,只要质数选的够大就能避免这个问题,同时遇到了哈希冲突就线性取址一下
#include<iostream>
#include<iomanip>
#include<cstdio>
#include<cstdlib>
using namespace std;
typedef long long ll;
const int p=1000003;//选一个比较大的质数,大于本题的最大数据即可
int n,c;
long long tot,a[200005];
struct hash{
ll x;//本位置存的数
int y;//出现的次数
}h[1000004];
ll l_abs(ll x){return x<0? -1*x:x;}//数据有负数,需要处理成绝对值
int find(ll x){
int temp=l_abs(x)%p;//先hash
while(h[temp].x&&h[temp].x!=x)//即假如哈希表此处已经存放数据并且数据不是temp
temp++,temp%=p;//线性寻址
return temp;
}
void push(ll x){
int y=find(x);//找到y的位置
h[y].y++;
h[y].x=x;
}
int check(ll x){
return h[find(x)].y;//返回y的数量
}
int main(){
scanf("%d%d",&n,&c);
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
push(a[i]);
}
for(int i=1;i<=n;i++){
tot+=check(a[i]-c);
}
printf("%lld",tot);
return 0;
}
这个题作为哈希入门很快乐,该介绍的都介绍了