map和哈希的周结

本文介绍了哈希和映射在处理字符串问题时的作用,通过分析HDU2648、洛谷P2879和P1381三道编程题目,展示了哈希函数在解决商店排名、奶牛高度和文章子段查找等复杂逻辑问题上的应用。同时,强调了判重、前缀和与尺取法在算法设计中的重要性。
摘要由CSDN通过智能技术生成

这个周学习了一些第九章字符串内容,做了一些题,就不写getline这种东西了,直接进入正题吧。
map和哈希都有判重的作用,在一些题目中两者都可以使用。map的代码相对简便但是时间复杂度高,哈希的代码相对较长但是时间复杂度极小,用于处理一些有特殊要求的题目。
1.hdu 2648
给出一些商店和其价格的变化,求i天后店铺的排名
(1)这题允许用map做,使用map存储商店的价格和变化,用迭代器遍历对商店的商品进行排名,比较容易就能得出。
(2)用字符串哈希的方法就比较繁琐了,经典的哈希函数有很多,但我暂时还是先把它当成模板使用吧。

哈希函数本质就是将字符串映射成一个具体的值,也可以说是商店的编号。但这个值通常很大,如果进行取余操作的话编号缩小了,但得到相同编号的可能性就变大了,所以要额外处理冲突的情况。此题就是另开了个链表list[n]解决冲突问题,对于相同的哈希值list[key],用循环对其进行操作,再将改变后的list[key][j]付值给新开的数组,这样就可以解决相同的哈希编号了。

#include<bits/stdc++.h> 
using namespace std;
const int N = 10005;
struct node{
	char name[35];
	int price;
};
vector<node>List[N]; //  用链表构造二维数组
unsigned int BKDRHash(char*str){      // 哈希函数 
	unsigned int seed = 31,key = 0;
	while(*str)
	key = key * seed + (*str++);
	return key & 0x7fffffff;
}
int main()
{
	int n,m,key,add,memory_price,rank,len;
	int p[N];
	char s[35];
	node t;
	while(cin >> n){
		for(int i = 0; i < N;i++)
		List[i].clear();
		for(int i = 0; i < n; i++){
			cin >> t.name;                 //输入店铺名称
			key = BKDRHash(t.name) % N;    //将hash值取余 
			List[key].push_back(t);		   //放入以list[key]为整体的数组中,不同下标存储相同哈希值但不同的商店
		}
		cin >> m;
    while(m--){
			rank = len = 0;
		for(int i = 0; i < n; i++){     
				cin >> add >> s;         //更改后的情况
				key = BKDRHash(s) % N;	//计算商店的哈希值
		for(int j = 0; j < List[key].size();j++){   //由于哈希函数取余可能存在很多冲突,因此用for循环计算冲突的情况
			if(strcmp(List[key][j].name,s) == 0){   //如果和店铺名称相同就改变它的价格
						List[key][j].price += add;
			if(strcmp(s,"memory") == 0){            //如果是memory商店则把它的值赋给memory
				memory_price = List[key][j].price;
			}else{
				p[len++] = List[key][j].price;      //如果不是memory商店就新开一个数组,把别的商店的值存起来,这样遍历起来方便一些
				 }
			break;
				}
			}
		}
		for(int i = 0; i < len; i++){              //开始遍历计算排名
				if(memory_price < p[i])rank++;
			}
			cout << rank + 1 << endl;
		}
	}
	return 0;
}

2.洛谷P2879
给出奶牛的个数n和最高奶牛的高度,接下来给出几组数据a,b。 在a和b之间的所有数据都比a小,b一定大于等于a。输出所有奶牛最高的高度

这道题并不是很难,用判重+前缀和就可以做出来,但思路并不是那么好想。使a+1位置的奶牛高度-1,b位置的奶牛高度+1,那么用前缀和的方式就可以实现a-b之间的奶牛高度比a和b小并且是最大值。接下来就是判重了,如果给出几组相同的a和b,用前缀和就让它们的高度减了好几次,但实际上减一次就够了。先考虑a和b的大小,随时进行交换。防止出现3.5和5.3这种影响判重的情况。然后用二维数组记录,之后有重复值也不让它们的高度进行减的操作就行。

#include <bits/stdc++.h>

using namespace std;

int main()
{   int c[10001],sum[10001];
    int n,i,h,r,a,b;
    cin>>n>>i>>h>>r;
    map<int,int>s[10001];
    for(int i=1;i<=r;i++)
       {

        cin>>a>>b;
        if(a>b) swap(a,b);
        if(s[a][b]==1) continue;  //判重
        else s[a][b]=1;
        c[a+1]--;                 //根据题目准备进行前缀和操作
        c[b]++;}
        for(int i=1;i<=n;i++)
        {
            sum[i]=c[i]+sum[i-1];  //用前缀和求高度
            cout<<sum[i]+h<<endl;
        }

}

3.洛谷 P1381
我们的灵梦想要记下几个单词,但它想要结合文章来记。在这文章中选取子段,输出这篇文章包含这些单词的个数,并且输出包含这些单词的最短子段的长度。
同样划区的两个题难度怎么差这么大。。。光是弄明白这题就花了大半个晚上。。。思考了好久无果后,开始学习题解了,我看了一个哈希+尺取法的方法,感觉逻辑还挺清晰的。
先构造哈希函数,需要对单词进行判重。查找单词个数遍历就可以了。求包含这些单词的最短子段长度需要用到尺取法,反复地推进区间的开头和末尾,来求取满足条件的最小区间的方法被称为尺取法。首先我们推进区间右端r,找出满足条件的第一个子串,接下来推进l。之后要考虑去除子段中多次出现同一单词的情况、子段中这些单词个数都为1但存在太多没用单词的情况。为了去除它们寻找最优解,因此要用while反复推进l和r。

#include <bits/stdc++.h>

#define ll long long
using namespace std;
using namespace std;
const int mod=1e6;
const int p=31;
const int INF=1e9;
int n,m,ans=INF,left,right,sum;
int a[1010],b[100010],appear[mod];//appear来记录单词在子段中出现几次
char s[110];
bool need[mod],vis[mod];        //need代表是否要背这个单词,vis用来计算文章要背的单词
int hash(char s[])             //哈希函数对相同单词进行判重
{
    int len=strlen(s);
    ll ret=0;
    for(int i=0;i<=len;i++)
    {
        ret*=p,ret+=s[i]-'a';
        ret%=mod;
    }
    return ret%=mod;
}
int main()
{    int i;
    cin>>n;
    for(i=1;i<=n;i++)
    {
        cin>>s;
        a[i]=hash(s);      //计算单词的hash值
        need[a[i]]=1;      //这些单词需要背
    }
    cin>>m;
    for(i=1;i<=m;i++)
    {
        cin>>s;
        b[i]=hash(s);
        if(need[b[i]]&&!vis[b[i]])  //第一个问题求解文章中有多少要背的单词
            sum++,vis[b[i]]=1;
    }
    if(!sum)              //如果一个都没有那也没必要计算第二问
    {
        cout<<"0"<<endl;
        cout<<"0"<<endl;
        return 0;
    }
    else cout<<sum<<endl;
    int l,r;
    l=1,r=1;
    while(1)
    {
        if(sum)         
        { if(r>m) break;
            if(need[b[r]])
            {
                if(!appear[b[r]]) sum--;//如果背的单词一次都没有出现过那么单词-1
                appear[b[r]]++; //记录它出现的次数
            }
         r++;    //计算包含这些单词的子串长度
        }
      else{
        while(!need[b[l]])  //记录不需要背的单词个数
                l++;
            if(l>m)
                break;
            ans=min(ans,r-l);  
            if(appear[b[l]]==1)  //推进区间用的
                sum++;
            if(appear[b[l]]>=1)
                appear[b[l]]--,l++;

    }}
    cout<<ans<<endl;
    return 0;
}

.
4.模拟:使用较简单的算法和数据结构的题目。通常有着复杂的逻辑,需要仔细按照题目要求一步步编写代码。看到洛谷上有很多算法标签显示模拟的题,开始还以为是模拟退火算法,原来完全不是一个意思。。。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值