STL map详解:从入门到精通【详细解析+例题训练】

嘻嘻今天学习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 【模板】字符串哈希

洛谷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 N10 M i ≈ 6 M_i≈6 Mi6 M m a x ≤ 15 Mmax\leq 15 Mmax15

对于 70 % 70\% 70% 的数据: N ≤ 1000 N\leq 1000 N1000 M i ≈ 100 M_i≈100 Mi100 M m a x ≤ 150 Mmax\leq 150 Mmax150

对于 100 % 100\% 100% 的数据: N ≤ 10000 N\leq 10000 N10000 M i ≈ 1000 M_i≈1000 Mi1000 M m a x ≤ 1500 Mmax\leq 1500 Mmax1500

样例说明:

样例中第一个字符串(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 保龄球

洛谷 P1918 保龄球

题目描述

DL 算缘分算得很烦闷,所以常常到体育馆去打保龄球解闷。因为他保龄球已经打了几十年了,所以技术上不成问题,于是他就想玩点新花招。

DL 的视力真的很不错,竟然能够数清楚在他前方十米左右每个位置的瓶子的数量。他突然发现这是一个炫耀自己好视力的借口——他看清远方瓶子的个数后从某个位置发球,这样就能打倒一定数量的瓶子。

  1. ◯ ◯ ◯ \bigcirc \bigcirc \bigcirc

  2. ◯ ◯ ◯   ◯ \bigcirc \bigcirc \bigcirc\ \bigcirc  

  3. ◯ \bigcirc

  4. ◯   ◯ \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 1n,Q1000,1ai,m105

对于 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 1n,Q100000,1ai,m109

解题思路

这道题很容易理解,可以很好的体现出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 210 个大写字母)和所在州的代码( 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 1N2×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

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] 不重复数字

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 n100,给出的数 ∈ [ 0 , 100 ] \in [0, 100] [0,100]

对于 60 % 60\% 60% 的数据, n ≤ 1 0 4 n \le 10^4 n104,给出的数 ∈ [ 0 , 1 0 4 ] \in [0, 10^4] [0,104]

对于 100 % 100\% 100% 的数据, 1 ≤ T ≤ 50 1 \le T\le 50 1T50 1 ≤ n ≤ 5 × 1 0 4 1 \le n \le 5 \times 10^4 1n5×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?

✅ 适用场景:
需要按键自动排序。

需要稳定查找性能。

键是自定义类型,且实现了比较函数。

❌ 不适用场景:

只需要快速查找,不关心顺序。

需要允许重复键。

煮啵高烧情况下写下这篇博客,如有错误的地方,就是脑子烧坏掉了,多多包容和理解~
希望这篇博客对你有所帮助!欢迎讨论和补充!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值