这个周学习了一些第九章字符串内容,做了一些题,就不写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.模拟:使用较简单的算法和数据结构的题目。通常有着复杂的逻辑,需要仔细按照题目要求一步步编写代码。看到洛谷上有很多算法标签显示模拟的题,开始还以为是模拟退火算法,原来完全不是一个意思。。。