浅谈 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;
}
突破极限,一旦放弃了就意味着结束,说不定身体里还隐藏着连自己都没有察觉到的力量,不要被所谓的极限所禁锢。