a-b=c problem【哈希|二分|map】

1 篇文章 0 订阅
1 篇文章 0 订阅

碰到一道很简单但是做法多样有趣的题,整理一下。

P1102 A-B 数对

做法一: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;
}

这个题作为哈希入门很快乐,该介绍的都介绍了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值