浅谈 STL

简介

STL是Standard Template Library的简称,中文名标准模板库,从根本上说,STL是一些“容器”的集合,这些“容器”有list,vector,set,map等,STL也是算法和其他一些组件的集合。这里的“容器”和算法的集合指的是世界上很多聪明人很多年的杰作。STL的目的是标准化组件,这样就不用重新开发,可以使用现成的组件。STL是C++的一部分,因此不用安装额外的库文件。

---------来自《百度百科》


那就直接开讲了呐

1.queue 队列

头文件:

#include <queue>
using namespace std;

简介:先进先出的无限长度数组。。。

基本操作函数:

  • q.push():从队尾入队
  • q.pop():从对头出队
  • q.front():队头元素
  • q.back():队尾元素
  • q.empty:判断是否为空
  • q.size():求队列长度

题目:Blah 数集

数学家高斯小时候偶然间发现一种有趣的自然数集合 Blah。对于以 a 为基的集合 Blah 定义如下:

1)a 是集合 Blah 的基,且 a 是 Blah 的第一个元素;
2)如果 x 在集合 Blah 中,则 2x+1 和 3x+1 也都在集合 Blah 中;
3)没有其他元素在集合 Blah 中了。
现在小高斯想知道如果将集合 Blah 中元素按照升序排列,第 n 个元素会是多少?注意:集合中没有重复的元素。

  • 输入格式
    一行两个正整数,分别表示集合的基 a 以及所求元素序号 n,1≤a≤50,1≤n≤1000000。

  • 输出格式
    一行一个正整数,表示集合 Blah 的第 n 个元素值。

样例输入

28 5437

样例输出

900585

分析
这是一道很显然的一道队列题目,重点是熟悉队列的操作

开两个队列,在每次入队后,找出两个队列队头较小的那一个,作为下一次入队的依据

因为是升序,所以题目就是要求第 n 小的,也就是我们第 n-1 次操作找到的两个队列队头较小的哪一个

AC代码

#include <cstdio>
#include <queue>
#include <algorithm>
using namespace std;

queue<int> x; // 2a + 1
queue<int> y; // 3a + 1

int main() {
	int a, n;
	scanf("%d %d", &a, &n);
	for(int i = 1; i < n; i++) {
        x.push(a * 2 + 1); // 分别入队
        y.push(a * 3 + 1);
        if(x.front() < y.front()) { // 取出两个序列的队头元素,进行比较
            a = x.front();
            x.pop(); // 先查找,再删除队头元素
        }
        else if(x.front() > y.front()) {
            a = y.front();
            y.pop();
        }
        else { // 如果相等,两边都要删除队头元素
            a = x.front();
            x.pop();
            y.pop();
        }
	}
	printf("%d", a);
	return 0;
}

2.priority_queue 优先队列

头文件:

#include <queue>
using namespace std;

简介:保证队头一定是最大值,或队头一定是最小值的队列,内部由二叉堆实现

基本操作函数:

  • q.push():从队尾入队
  • q.pop():从对头出队
  • q.top():队头元素
  • q.empty:判断是否为空
  • q.size():求队列长度

特别注意:优先队列默认大根堆,如果要改为小根堆,需要建立自定义结构体,然后重载运算符

struct node {
	int id, v;
	bool operator<(const node x) const {return v > x.v;}
};
priority_queue <node> q;

当然也可以写成

priority_queue<int, vector<int>, greater<int> > q;

题目:有序表的最小和

题目描述
给出两个长度为 n 的有序表 A 和 B,在 A 和 B 中各任取一个元素,可以得到 n*n 个和,求这些和中最小的 n 个。

输入格式
第 1 行包含 1 个整数正 n(n≤400000)。 第 2 行与第 3 行分别有 n 个整数,各代表有序表 A 和 B。一行中的每两个整数之间用一个空格隔开,大小在长整型范围内,数据保证有序表单调递增。

输出格式
输出共 n 行,每行一个整数,第 i 行为第 i 小的和。 数据保证在 long long 范围内。

样例输入

3
1 2 5
2 4 7

样例输出

3
4
5

分析

可以枚举所有和,再压入小根堆优先队列,因为优先队列的性质,所以输出前n个队头元素即可。但因为n的范围是400000,所以这种n方的算法一定会TLE。。。

由于两个已知数列的有序性,所以可得

第一行    A[1]+B[1] ≤ A[1]+B[2] ≤ A[1]+B[3] ≤ ······
第二行    A[2]+B[1] ≤ A[2]+B[2] ≤ A[2]+B[3] ≤ ······
······
第n行     A[n]+B[1] ≤ A[n]+B[2] ≤ A[n]+B[3] ≤ ······

那就先把每一行的第一个(即最小值)压入优先队列,取出队头元素(最小值)并输出,如果取的是第i行的元素,就把第i行的下一个元素压入,让堆中始终保持n个元素和。

AC代码

#include <cstdio>
#include <queue>
#include <algorithm>
using namespace std;

const int MAXN = 400005;
long long a[MAXN], b[MAXN], c[MAXN];

struct node {
	int id, v; // id代表是第几行,v代表实际值
	bool operator<(const node x) const {return v > x.v;} // 运算符重载
};
priority_queue <node> q; // 优先队列的定义

int main() {
	int n, m;
	scanf ("%d", &n);
	for(int i = 1; i <= n; i++) scanf ("%lld", &a[i]);
	for(int i = 1; i <= n; i++) scanf ("%lld", &b[i]);	
	for(int i = 1; i <= n; i++) {
		c[i] = 2;
		node t;
		t.v = a[i] + b[1]; // 将每行第一个进入队列
		t.id = i; // 存储是第几行的
		q.push(t);
	}
	for(int i = 1; i <= n; i++) {
		node t = q.top(); // 取出队头元素
		q.pop(); 
		printf("%d\n", t.v);
		t.v = a[t.id] + b[c[t.id]++]; // 计算出下一个
		q.push(t); // 进入队列
	}
	return 0;
}

3.stack 栈

头文件:

#include <stack>
using namespace std;

简介:后进先出的无限长度数组。。。

基本操作函数:

  • s.push():从栈顶入栈
  • s.pop():从栈顶出栈
  • s.top():栈顶元素
  • s.empty:判断是否为空

题目:简单计算器

题目描述
读入一个只包含+、-、*、/、的非负整数计算表达式,计算该表达式的值。

  • 输入格式
    测试数据有多组,每组占一行。
    每行不超过200个字符,整数和运算符之间用一个空格分隔。
    没有非法表达式。

    当一行中只有一个0时,表示输入结束,相应的结果不要输出。

  • 输出格式
    对于每组测试数据输出一行,即该表达式的值,精确到小数点后两位。

输入样例

30 / 90 - 26 + 97 - 5 - 6 - 13 / 88 * 6 + 51 / 29 + 79 * 87 + 57 * 92
0

输出样例

12178.21

分析
当读入到一个符号的时候,从数字栈顶揪出一个数,进行计算,再重新压入栈呐~

AC代码

#include <cstdio>
#include <stack>
using namespace std;
stack<double> s; // 栈的定义

int main() {
	double x;
	char c;
	while(scanf ("%lf%c", &x, &c)) {
		if(x == 0 && c != ' ') return 0;
		double m;
		char a, b;
		s.push(x);
		while(scanf ("%c %lf%c", &a, &m, &b) != EOF) { // 输入
			if(a == '+') s.push(m); // 如果是加号,压入正m
			else if(a == '-') s.push(-m); // 是减号,压入负m
			else if(a == '*' && !s.empty()) { // 如果是乘号,且数字栈不为空
				double t = s.top(); // 取出一个
				s.pop();
				t *= m;
				s.push(t); // 压入 t*m
			}
			else if(a == '/' && !s.empty()) { // 如果是除号,且数字栈不为空
				double t = s.top(); // 取出一个
				s.pop();
				t /= m;
				s.push(t); // 压入 t/m
			}			
			if(b != ' ') break;
		}
		double ans = 0;
		while(!s.empty()) {
			ans += s.top(); // 累计数字栈里的答案即可
			s.pop();
		}
		printf("%.2lf\n", ans);
	}
	return 0;
}

4.vector 动态数组

头文件:

#include <vector>
using namespace std;

简介:无限长度的数组。。。

基本操作函数:

  • v.push_back():插入元素到数组的尾部
  • v.pop_back():删除数组尾部的元素
  • v.size():求数组长度
  • v.empty():判断数组是否为空
  • v.clear():把数组清空
  • 迭代器:迭代器就像STL的指针,返回一个地址,可以用*操作符得到其对应的具体的值
    声明方法:vector::iterator it
  • v.begin():返回指向数组第一个元素的迭代器
  • v.end():返回指向数组最后一个元素的迭代器
  • v.front():返回数组第一个元素
  • v.back():返回数组最后一个元素

题目:上网统计

题目描述
在一个网络系统中有N个用户1≤N≤1000、M次上网记录1≤M≤5000。每个用户可以自己注册一个用户名,每个用户名是一个只含小写字母的字符串。每个上网的账号每次上网都会浏览网页,网页名是一串只含小写字母的字符串,每次上网日志都会留下记录,现在请你统计一次上网日志中,每个用户浏览了多少个网页。(输出按照输入顺序输出)

  • 输入格式
    第一行N和M 第2行到第M+1行为M条上网日志,每行两个字符串,用空格隔开

  • 输出格式
    N个ID的上网记录,具体看样例

样例输入

5 7
guomao wangyi
lifan tengxun
zhoushijian souhu
zhangshilin tengxun
guomao souhu
zhoushijian wangyi
liuyang bilibili

样例输出

guomao wangyi souhu
lifan tengxun
zhoushijian souhu wangyi
zhangshilin tengxun
liuyang bilibili

分析
此题GM讲过的哦~
看看各种函数及迭代器的使用。。。

AC代码

#include <cstdio> 
#include <string> 
#include <iostream> 
#include <vector>
using namespace std;

const int MAXN = 1005;
vector <string> v[MAXN]; // 动态数组的定义
struct node{
	int index;
	string c;
} s[MAXN];

int main() {
	int n, m, t = 0;
	scanf ("%d %d", &n, &m);
	for(int i = 1; i <= m; i++) {
		string ch, k;
		getchar();
		cin >> ch;
		cin >> k;
		int index_ = 0;
		for(int j = 1; j <= t; j++) {
			if(ch == s[j].c) 
				index_ = s[j].index;
		}
		if(index_ == 0) {
			t++;
			index_ = t;
			s[index_].index = t;
			s[index_].c = ch;			
		}
		v[index_].push_back(k); // 在最后插入一个元素
	} 
	for(int i = 1; i <= n; i++) {
		cout << s[i].c << " ";
		for(vector<string>::iterator it = v[s[i].index].begin(); it != v[s[i].index].end(); it++) { 
		// 利用迭代器进行输出,将其初值置为指向第一个元素的迭代器,如果当前迭代器没有指向最后一个元素,迭代器加加
			cout << *it << " "; // 输出当前迭代器对应的具体的值
		}
		cout << endl;
	}
	return 0;
}

5.map

头文件:

#include <map>
using namespace std;

简介:映射,可以看作数组下标为任意类型的数组

基本操作函数:

  • v.empty():判断数组是否为空
  • v.clear():把数组清空
  • 迭代器声明方法:map<int, int>::iterator it
    且 it 对应的具体的值是一个pair
  • v.begin():返回指向数组第一个元素的迭代器
  • v.end():返回指向数组最后一个元素的迭代器
  • v.insert():插入一个元素

题目:T1 查字典

题目描述
gm英语非常不好,为了应对全国英文四级考试,他手里有一本英语字典,现在有很多单词要查。请编写程序帮助他快速找到要查的单词所在的页码。

  • 输入格式
    第一行1个整数N,N≤10000,表示字典中一共有多少单词。

    接下来每两行1个单词,其中:第一行是长度≤100的字符串,表示这个单词,全是小写字母,单词不会重复。 第二行是1个整数,表示这个单词在字典中的页码。

    接下来是一个整数M,M≤N,表示要查的单词数。 接下来M行,每行一个字符串,表示要查的单词,保证在字典中存在。

  • 输出格式
    M行,每行1个整数,表示第i个单词在字典中的页码。

样例输入

2
scan
10
word
15
2
scan
word

样例输出

10
15

分析
是裸题诶……以单词为下标保存页码的数组就可以了

AC代码

#include <cstdio>
#include <map>
#include <string>
#include <iostream>
using namespace std;

map<string, int> mp; // 映射的定义

int main() {
	int n;
	scanf ("%d", &n);
	for(int i = 1; i <= n; i++) {
		string s;
		cin >> s;
		int x;
		cin >> x;
		mp[s] = x;
	// 可以像数组一样赋值
	// 此题也可以利用 pair 写为 mp.insert(make_pair(s,x));
	}
	int m;
	scanf ("%d", &m);
	for(int i = 1; i <= m; i++) {
		string s;
		cin >> s;
		printf("%d\n", mp[s]);
	} 
	return 0;
}
T2 Let the Balloon Rise

小气球~~气球~

题目描述
在ACM比赛中,你每解决一道题,你就可以获得一个气球,不同颜色的气球代表你解决了不同的问题。在GM同学参加的一场ACM比赛中,他发现场面上有N个气球,并熟练的说出了气球的颜色。

请你编写一个程序,找出气球数量最多的颜色。

  • 输入格式
    有多组样例输入。

    每组样例第一行输入一个整数N (0 < N ⇐ 1000) ,代表一共有N个气球。若N=0,则代表输入结束。

    接下来N行每行输入一个不多于15个字母的字符串代表颜色。

  • 输出格式
    对于每组样例数据,在单独的一行内输出数量最多的那种颜色的气球。(数据保证输出是唯一的)

样例输入

5
green
red
blue
red
red
3
pink
orange
pink
0

样例输出

red
pink

分析
还是裸a!~!!
以颜色为下标存储出现次数的数组

#include <cstdio>
#include <map>
#include <string>
#include <iostream>
using namespace std;

const int MAXN = 10005;
string s[MAXN];

int main() {
	int n;
	while(scanf ("%d", &n) != EOF) {
		if(n == 0) break;
		map<string, int> mp; // map 的定义
		for(int i = 1; i <= n; i++) {
			cin >> s[i];
			mp[s[i]]++; // 颜色 s[i] 出现的次数增加
		}
		string ch;
		int ma = 0;
		for(int i = 1; i <= n; i++) {
			if(mp[s[i]] >= ma) {
				ma = mp[s[i]];
				ch = s[i];
			}
		}
		cout << ch << endl;
	}
	return 0;
}

6.set 有序集合

头文件:

#include <set>
using namespace std;

简介:set 有序的无重复的集合,multiset 有序的可重复的集合

基本操作函数:(set 和 multiset 相同)

  • s.size():求集合长度
  • s.empty():判断集合是否为空
  • s.clear():把集合清空
  • 迭代器声明方法:set::iterator it
  • s.begin():返回指向集合第一个元素的迭代器
  • s.end():返回指向集合最后一个元素的迭代器
  • s.insert():插入一个元素
  • s.count():返回集合中等于某个数的元素个数

特别注意:set 和 multiset 也需要重载运算符


题目:【STL综合】题海战

题目描述
某信息学奥赛教练经验丰富,他的内部题库有 m 道题。他有 n 个学生,第 i 个学生已经做过p[i]道题。由于马上要进行noip考试,该教练准备举行 k 场比赛和训练。每场比赛或训练都会有一些他的学生参加,但是如何选题令他非常烦恼。对于每场比赛,他要保证所出的题没有任何一道已有任何一个学生做过;而对于每场训练,他要保证所出的所有题都被每一个参赛学生做过。

  • 输入格式
    第1行2个正整数n和m,表示学生数和题库中的题目总量。

    第2~n+1行,先是1个正整数p,然后p个整数表示第i个学生的做题记录(可以重复做同一道题)。

    第n+2行,1个正整数k,表示要举行比赛和训练的总场数(可能有学生重复报名)。

    接下来的k行,每行的第1个整数type表示是训练或者比赛(1为训练,0为比赛)。第二个数q表示参赛学生数,然后q个正整数表示参赛学生编号。每一行的两个数之间有一个空格。

  • 输出格式
    共k行,每行表示本次训练或比赛可选的最多题目(由小到大排序,中间用一个空格隔开,如果没有输出一个空行)。

样例输入

5 10
2 3 7
1 3
2 4 7
3 3 6 10
7 1 2 3 4 7 8 9
6
0 3 3 4 5
0 3 1 3 4
1 2 1 3
0 1 5
1 1 2
1 2 3 5

样例输出

5
1 2 5 8 9
7
5 6 10
3
4 7

分析
利用动态数组存储每一个学生做过的题目
在输出时利用映射求出每个参加训练的学生都做过的题
利用有序集合求出每个参加比赛的学生都没做过的题

#include <cstdio>
#include <set>
#include <map>
#include <vector>
using namespace std;

const int MAXN = 10005;
vector<int> v[MAXN]; // 动态数组的定义
set<int> s; // 有序集合的定义
map<int, int> mp; // 映射的定义

int main() {
	int n, m;
	scanf ("%d %d", &n, &m);
	for(int i = 1; i <= n; i++) {
		int n_;
		scanf ("%d", &n_);
		for(int j = 1; j <= n_; j++) {
			int x;
			scanf ("%d", &x);
			v[i].push_back(x);
		}
	}
	int k;
	scanf ("%d", &k);
	for(int i = 1; i <= k; i++) {
		int flag;
		s.clear();
		mp.clear();
		scanf ("%d", &flag);
		if(flag == 0) {
			int n_;
			scanf ("%d", &n_);
			for(int j = 1; j <= n_; j++) {
				int stu;
				scanf ("%d", &stu);
				for(int k_ = 0; k_ < v[stu].size(); k_++) // 统计每道题被几个学生做过
					mp[v[stu][k_]]++;
			}	
			for(int j = 1; j <= m; j++) { 
				if(mp[j] == n_) // 如果都做过就输出
					printf("%d ", j);
			}					
			printf("\n");
		}
		else {
			
			int n_;
			scanf ("%d", &n_);
			for(int j = 1; j <= n_; j++) {
				int stu;
				scanf ("%d", &stu);
				for(int k_ = 0; k_ < v[stu].size(); k_++)
					s.insert(v[stu][k_]); // 加入有序集合
			}	
			bool flag[MAXN] = {0};
			for(set<int>::iterator it = s.begin(); it != s.end(); it++) // 迭代器遍历,把所有有学生做过的题目标记
				flag[*it] = true;
			for(int j = 1; j <= m; j++) {
				if(flag[j] == false) // 未被标记的即可输出
					printf("%d ", j); 
			}			
			printf("\n");			
		}
	} 
	return 0;
}

突破极限,一旦放弃了就意味着结束,说不定身体里还隐藏着连自己都没有察觉到的力量,不要被所谓的极限所禁锢。

posted @ 2020-10-24 11:25  STrAduts  阅读(190)  评论(0编辑  收藏  举报