嘻嘻今天学习map!
文章目录
提示:若有错误或不足请指点~
一、map的基本特点
STL 中的 map 是关联容器,提供高效的键值对存储与查找.核心特点:
键值对存储:每个元素是 pair<const Key, T>(键不可修改,值可修改)
唯一键:每个key只能出现一次(若需重复key,使用multimap)
自动排序:默认按key升序排列(可用自定义比较函数)
高效操作:插入、删除、查找的时间复杂度均为O(log n)
二、map的使用
map的定义形式:map<key类型,value类型>m(map容器的名字)
eg.map<int,int>m;
m.begin() 返回指向第一个元素的迭代器
m.end() 返回指向末尾元素的迭代器
m.clear() 删除所有元素,即清空迭代器
m.count() 返回指定元素出现的次数
m.empty() 判断是否为空,如果为空返回true
m.insert() 插入元素
m.find() 查找一个元素,找到返回指向key的迭代器,未找到返回end()
m.erase(it) 删除it所指元素元素
m.erase(start,end) 删除区间[start,end)之间的元素(前闭后开)
m.insert() 插入元素
m.rbegin() 返回逆向迭代器,指向末尾
m.rend() 返回指向开头之前位置的迭代器
m.size() 返回map中元素的个数
m.lower_bound() 返回键值>=给定元素的第一个位置
m. upper_bound() 返回键值>给定元素的第一个位置
三、例题训练
1).洛谷P3370 【模板】字符串哈希
题目描述
如题,给定 N N N 个字符串(第 i i i 个字符串长度为 M i M_i Mi,字符串内包含数字、大小写字母,大小写敏感),请求出 N N N 个字符串中共有多少个不同的字符串。
友情提醒:如果真的想好好练习哈希的话,请自觉。
输入格式
第一行包含一个整数 N N N,为字符串的个数。
接下来 N N N 行每行包含一个字符串,为所提供的字符串。
输出格式
输出包含一行,包含一个整数,为不同的字符串个数。
输入
5
abc
aaaa
abc
abcc
12345
输出
4
说明/提示
对于 30 % 30\% 30% 的数据: N ≤ 10 N\leq 10 N≤10, M i ≈ 6 M_i≈6 Mi≈6, M m a x ≤ 15 Mmax\leq 15 Mmax≤15。
对于 70 % 70\% 70% 的数据: N ≤ 1000 N\leq 1000 N≤1000, M i ≈ 100 M_i≈100 Mi≈100, M m a x ≤ 150 Mmax\leq 150 Mmax≤150。
对于 100 % 100\% 100% 的数据: N ≤ 10000 N\leq 10000 N≤10000, M i ≈ 1000 M_i≈1000 Mi≈1000, M m a x ≤ 1500 Mmax\leq 1500 Mmax≤1500。
样例说明:
样例中第一个字符串(abc)和第三个字符串(abc)是一样的,所以所提供字符串的集合为{aaaa,abc,abcc,12345},故共计4个不同的字符串。
解题思路
这是一道非常简单基础的模板题,我们可以将每个字符串存入map中记录它的个数,因为题目中问不同的字符串有多少个,我们只需注意相同的字符串只记录一次,避免重复即可。
代码实现
#include<bits/stdc++.h>
using namespace std;
map<string,int>mp;//定义key类型为string存储字符串,int类型记录个数
int main()
{
int n,ans=0;//ans统计不同字符串的个数
string s;
cin>>n;
while(n--)
{
cin>>s;
if(mp[s]==0)//避免重复记录
mp[s]++,ans++;
}
cout<<ans<<endl;
return 0;
}
2).洛谷 P1918 保龄球
题目描述
DL 算缘分算得很烦闷,所以常常到体育馆去打保龄球解闷。因为他保龄球已经打了几十年了,所以技术上不成问题,于是他就想玩点新花招。
DL 的视力真的很不错,竟然能够数清楚在他前方十米左右每个位置的瓶子的数量。他突然发现这是一个炫耀自己好视力的借口——他看清远方瓶子的个数后从某个位置发球,这样就能打倒一定数量的瓶子。
-
◯ ◯ ◯ \bigcirc \bigcirc \bigcirc ◯◯◯
-
◯ ◯ ◯ ◯ \bigcirc \bigcirc \bigcirc\ \bigcirc ◯◯◯ ◯
-
◯ \bigcirc ◯
-
◯ ◯ \bigcirc\ \bigcirc ◯ ◯
如上图,每个 “ ◯ \bigcirc ◯” 代表一个瓶子。如果 DL 想要打倒 3 3 3 个瓶子就在 1 1 1 位置发球,想要打倒 4 4 4 个瓶子就在 2 2 2 位置发球。
现在他想要打倒 m m m 个瓶子。他告诉你每个位置的瓶子数,请你给他一个发球位置。
输入格式
第一行包含一个正整数 n n n,表示位置数。
第二行包含 n n n 个正整数 a i a_i ai ,表示第 i i i 个位置的瓶子数,保证各个位置的瓶子数不同。
第三行包含一个正整数 Q Q Q,表示 DL 发球的次数。
第四行至文件末尾,每行包含一个正整数 m m m,表示 DL 需要打倒 m m m 个瓶子。
输出格式
共 Q Q Q 行。每行包含一个整数,第 i i i 行的整数表示 DL 第 i i i 次的发球位置。若无解,则输出 0 0 0。
输入
5
1 2 4 3 5
2
4
7
输出
3
0
说明/提示
【数据范围】
对于 50 % 50\% 50% 的数据, 1 ≤ n , Q ≤ 1000 , 1 ≤ a i , m ≤ 1 0 5 1 \leq n, Q \leq 1000, 1 \leq a_i, m \leq 10^5 1≤n,Q≤1000,1≤ai,m≤105。
对于 100 % 100\% 100% 的数据, 1 ≤ n , Q ≤ 100000 , 1 ≤ a i , m ≤ 1 0 9 1 \leq n,Q \leq 100000, 1 \leq a_i, m \leq 10^9 1≤n,Q≤100000,1≤ai,m≤109。
解题思路
这道题很容易理解,可以很好的体现出map的优势,我们可以定义map<int,int>mp;key值存保龄球的数量,值是对应的位置,这样就可以直接通过数量找到对应的位置。
代码实现
#include<bits/stdc++.h>
using namespace std;
map<int,int>mp;
int main()
{
int n,x,q,num;
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>x;
mp[x]=i;
}
cin>>q;
while(q--)
{
cin>>num;
cout<<mp[num]<<endl;
}
return 0;
}
欧克,下一道~
3).洛谷P3405 [USACO16DEC] Cities and States S
P3405 [USACO16DEC] Cities and States S
题目描述
Farmer John 有若干头奶牛。为了训练奶牛们的智力,Farmer John 在谷仓的墙上放了一张美国地图。地图上表明了每个城市及其所在州的代码(前两位大写字母)。
由于奶牛在谷仓里花了很多时间看这张地图,他们开始注意到一些奇怪的关系。例如,FLINT 的前两个字母就是 MIAMI 所在的 FL
州,MIAMI 的前两个字母则是 FLINT 所在的 MI
州。
确切地说,对于两个城市,它们的前两个字母互为对方所在州的名称。
我们称两个城市是一个一对「特殊」的城市,如果他们具有上面的特性,并且来自不同的州。对于总共 N N N 座城市,奶牛想知道有多少对「特殊」的城市存在。请帮助他们解决这个有趣的地理难题!
输入格式
输入共 N + 1 N + 1 N+1 行。
第一行一个正整数 N N N,表示地图上的城市的个数。
接下来 N N N 行,每行两个字符串,分别表示一个城市的名称( 2 ∼ 10 2 \sim 10 2∼10 个大写字母)和所在州的代码( 2 2 2 个大写字母)。同一个州内不会有两个同名的城市。
输出格式
输出共一行一个整数,代表特殊的城市对数。
输入
6
MIAMI FL
DALLAS TX
FLINT MI
CLEMSON SC
BOSTON MA
ORLANDO FL
输出
1
说明/提示
对于 100 % 100\% 100% 的数据, 1 ≤ N ≤ 2 × 1 0 5 1 \leq N \leq 2 \times 10 ^ 5 1≤N≤2×105,城市名称长度不超过 10 10 10。
解题思路
输入两个字符串后,我们可以先将字符串前两位转化为对应的26进制数相加,通过判断数是否相等进而判断是否符合情况,另外注意如果这两个字符串前两个字符都相同,则没有配对的城市,要排除。有了这个思路,我们可以定义一个map<int,int>m[100005]的map数组来实现。
代码实现
#include<bits/stdc++.h>
using namespace std;
map<int,int>mp[100005];
int main()
{
string s,c;
int n,ans=0;
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>s;
cin>>c;
int a=(s[0]-'A')*26+s[1]-'A';//转换为对应的26进制数,也可以直接写成s[0]*26+s[1]
int b=c[0]*26+c[1];
mp[a][b]++;//记录数量
if(a!=b)//排除城市前两个字母和省的字母相同的情况
ans+=mp[b][a];//注意b,a的位置,这样才能找到互为对方所在州的名称
}
cout<<ans<<endl;
return 0;
}
4).[codeforces] Game of Mathletes
题目描述
解题思路
因为Alice先选数,所以不占优势,Alice选后只要有符合条件的数,Bob就一定会选,但如果剩下的数中没有和Alice选择的数相加等于k的值,那Bob也会尽量选择一个不可能符合条件的数。
因为n是偶数,那么Bob会将最后一个数选掉。
同时要注意如果k等于一的话,不可能有两个数相加等于1,所以直接特判输出0.
有了上述思路,这道题用map来做也是相当方便嘟,
代码实现
#include<bits/stdc++.h>
using namespace std;
map<int,int>mp;
const int N=2e5+5;
int t,n,k,a[N];
int main()
{
cin>>t;
while(t--)
{
int ans=0;
cin>>n>>k;
for(int i=1;i<=n;i++)
cin>>a[i];
for(int i=1;i<=n;i++)
{
if(mp[k-a[i]]>0)//如果有符合的数就选
{
mp[k-a[i]]--;//用一个少一个
ans++;//记录相加等于k的个数
}
else
mp[a[i]]++;//如果没有符合的数,就存着
}
cout<<ans<<endl;
mp.clear();//记得清空mp中残留的数,否则会干扰下一个测试用例
}
return 0;
}
5).P4305 [JLOI2011] 不重复数字
题目描述
给定 n n n 个数,要求把其中重复的去掉,只保留第一次出现的数。
本题有多组数据。
输入
第一行一个整数 T T T,表示数据组数。
对于每组数据:
第一行一个整数 n n n。
第二行 n n n 个数,表示给定的数。
输出
对于每组数据,输出一行,为去重后剩下的数,两个数之间用一个空格隔开。
输入
2
11
1 2 18 3 3 19 2 3 6 5 4
6
1 2 3 4 5 6
输出
1 2 18 3 19 6 5 4
1 2 3 4 5 6
说明/提示
对于 30 % 30\% 30% 的数据, n ≤ 100 n \le 100 n≤100,给出的数 ∈ [ 0 , 100 ] \in [0, 100] ∈[0,100]。
对于 60 % 60\% 60% 的数据, n ≤ 1 0 4 n \le 10^4 n≤104,给出的数 ∈ [ 0 , 1 0 4 ] \in [0, 10^4] ∈[0,104]。
对于 100 % 100\% 100% 的数据, 1 ≤ T ≤ 50 1 \le T\le 50 1≤T≤50, 1 ≤ n ≤ 5 × 1 0 4 1 \le n \le 5 \times 10^4 1≤n≤5×104,给出的数在 32 32 32 位有符号整数范围内。
解题思路
一开始觉得这题好简单,直接用map将整数存进去并输出,有重复的不存也不输出,
可是只有60分,经过我几天后再来做这道题发现,原来是cin cout搞的鬼!!!,一定要加上ios::sync_with_stdio(0),cin.tie(0),cout.tie(0) !!!
代码实现
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
using namespace std;
const int N=50005;
int a[N];
map<int,int>mp;
void solve()
{
int n;
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
if(mp[a[i]]==0)
{
mp[a[i]]++;
cout<<a[i]<<" ";
}
}
cout<<endl;
mp.clear();//记得清空
}
int main()
{
IOS;
int t;
cin>>t;
while(t--)solve();
return 0;
}
这道题用set也可以写
#include<bits/stdc++.h>
#define endl '\n'
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)//一定要写,不然还会TLE!!!
#define int long long
using namespace std;
signed main()
{
IOS;
int t,n,num;
set<int>s;
cin>>t;
while(t--)
{
cin>>n;
while(n--)
{
cin>>num;
if(s.find(num)!=s.end())//如果集合中有这个数,则不做处理
continue;
else
{
s.insert(num);//若没有,则存入并输出
cout<<num<<" ";
}
}
cout<<endl;
s.clear() ;
}
return 0;
}
四、总结 何时用map?
✅ 适用场景:
需要按键自动排序。
需要稳定查找性能。
键是自定义类型,且实现了比较函数。
❌ 不适用场景:
只需要快速查找,不关心顺序。
需要允许重复键。
煮啵高烧情况下写下这篇博客,如有错误的地方,就是脑子烧坏掉了,多多包容和理解~
希望这篇博客对你有所帮助!欢迎讨论和补充!