一、vector
介绍:
vector是一个能够存放任意类型的动态数组,能够增加和压缩数据。
在局部区域不可以长数组,但是可以开vector。
使用的时候需要加上头文件
#include<vector>
初始化:
没有指定大小的初始化:
vector<int> a; //相当于开了一个名称为a的,存放整型的数组
vector<double> b; //相当于开了一个名称为b的,存放浮点数的数组。
vector<pair<int,int>> c; //存放pair的数组,常用与存点。
vector<pair<pair<int,int>,int>> d;
//常用于需要输出步数的搜索,前面存放点,后面存放当前步数。
vector<pair<pair<int,int>,pair<int,int>>> e;
//常用于需要输出路径的搜索,两个pair分别存放当前路径点和路径中的上一个点
指定大小的初始化:
int n;
vector<int> a(n); //开一个初始化长度为n的整型数组
vector<int> b(n, 1); //开一个初始化长度为n,且每个元素都为1的整形数组
vector<int> c{1, 2, 3}; //开一个初始化中有三个元素1,2,3的整形数组
vector<int> d(a); //开一个和a一模一样的数组
固定长度的二维数组创建:
int n, m;
vector<vector<int>> a(n, vector<int> (m, 1));
//这句话的意思相当于是说,开了一个有n行,m列,且初始时每个元素都是1的二维数组
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
cin >> a[i][j];
}
}
不固定长度的二维数组创建(每一行的元素个数可以不同):
int n; //表示有n行
vector<vector<int>> b;
for (int i = 0; i < n; i++) {
vector<int> temp;
int m; cin >> m; //临时开一个数组,这个数组有m个元素
for (int j = 0; j < m; j++) {
int x; cin >> x;
temp.push_back(x); //在临时数组中挨个插入元素
}
b.push_back(temp); //将这个临时数组插入到二维数组中
}
函数调用
a.pushback(x);时间复杂度\(O(1)\):
在数组最后的位置插入元素x。
vector<int> a;
int x; a.push_back(x); //在数组的最后插入指定元素x;
vector<pair<int, int>> b;
int n, m; b.push_back({n, m});
a.pop_back();
a.size();时间复杂度\(O(1)\):
返回数组中含有的实际数据个数。
vector<int> a{1, 2, 3, 4, 5};
cout << a.size();
//输出结果为 5
a.pop_back();时间复杂度\(O(1)\):
删除数组中的最后一个元素。
vector<int> a{1, 2, 3};
for (int i = 0; i < a.size(); i++) cout << a[i] << " ";
//输出结果为 1 2 3
a.pop_back();
for (int i = 0; i < a.size(); i++) cout << a[i] << " ";
//输出结果为 1 2
a.empty();时间复杂度\(O(1)\):
查看数组中是否含有元素。如果是空数组,则返回真。如果有数据,则返回假。
vector<int> a;
if (a.empty()) cout << "空" << " ";
else cout << "非空" << " ";
a.push_back(1);
if (a.empty()) cout << "空" << " ";
else cout << "非空" << " ";
//输出结果为 空 非空
a.front(); a.back();时间复杂度\(O(1)\):
返回数组中第一个和最后一个数据。
vector<int> a{10, 20, 30, 40, 50};
cout << a.front() << " " << a.back() << endl;
//输出结果为 10 50
a.begin(); a.end();时间复杂度\(O(1)\):
返回数组中第一个数据地址。返回数组中最后一个数据的后一个位置的地址。
vector<int> a{10, 20, 30, 40, 50};
vector<int>::iterator it1 = a.begin();
vector<int>::iterator it2 = a.end();
cout << *it1 << " " << *(it2 - 1) << endl;
//输出结果为 10 50
另,在vector中用sort的方法:
vector<int> a{5, 2, 7, 1, 8};
sort(a.begin(), a.end());
for (int i = 0; i < a.size(); i++) cout << a[i] << " ";
//对比常规数组。
int b[5] = {5, 2, 7, 1, 8};
sort(b, b + 5);
for (int i = 0; i < 5; i++) cout << b[i] << " ";
//输出结果都为 1 2 5 7 8
a.insert(it, x);时间复杂度\(O(n)\):
在迭代器it后面插入一个元素x。
vector<int> a{10, 20, 30, 40, 50};
a.insert(a.begin() + 1, 666);
for (int i = 0; i < a.size(); i++) cout << a[i] << " ";
//输出结果为 10 666 20 30 40 50
a.erase(start, end);时间复杂度\(O(n)\):
start, end为迭代器。删除数组中从start到end中的元素。包括start,不包括end。
vector<int> a{10, 20, 30, 40, 50};
a.erase(a.begin() + 1, a.begin() + 3);
for (int i = 0; i < a.size(); i++) cout << a[i] << " ";
//输出结果为 10 40 50
二、queue:
介绍:
queue是队列,先进入队列中的数据先出来,就像排队一样,先去排队的人最先排完队。
常用与单调队列或宽度优先搜索等算法。
使用的时候需要加上头文件:
#include<queue>
初始化:
queue<int> q1;
queue<pair<int, int>> q2;
函数调用:
q.push(x); q.pop(); q.front(); q.back(); q.size(); q.empty() 六个函数时间复杂度均为\(O(1)\)。
queue<int> q;
int x; q.push(x); //在队尾插入元素x
q.pop(); //删除队首的元素
q.push(10); q.push(20); q.push(30);
cout << q.front() << " " << q.back() << endl;
//返回队首元素 和 返回队尾元素
//输出结果为 10 30
cout << q.size() << endl;
//返回队列中的元素个数
//输出结果为 3
if (q.empty()) cout << "空" << endl;
else cout << "非空" << endl;
//如果是空队列,则返回值为真。
//如果队列中含有元素,则返回值为假。
//输出结果为 非空
例题:
①:acwing 829. 模拟队列
原题指路:829. 模拟队列 - AcWing题库
代码一:(模拟版)
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int q[N];
int hh;
int tt = - 1;
void solve() {
int m; cin >> m;
while(m--) {
string op;
cin >> op;
if (op == "push") {
int x; cin >> x;
q[++tt] = x;
}
else if (op == "pop") {
hh++;
}
else if(op == "empty") {
if (hh > tt) cout << "YES" << endl;
else cout << "NO" << endl;
}
else {
cout << q[hh] << endl;
}
}
}
int main() {
solve();
return 0;
}
代码二:(队列版)
#include<bits/stdc++.h>
using namespace std;
void solve() {
queue<int> q;
int m; cin >> m;
while(m--) {
string op; cin >> op;
if (op == "push") {
int x; cin >> x;
q.push(x);
}
else if (op == "pop") {
q.pop();
}
else if (op == "empty") {
if (q.empty()) cout << "YES" << endl;
else cout << "NO" << endl;
}
else {
cout << q.front() << endl;
}
}
}
int main() {
solve();
return 0;
}
②:Codeforces Round 295 (Div. 2) B
原题指路:Problem - 520B - Codeforces
代码:
#include<bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;
const int N = 2e4 + 10;
int n, m;
bool vis[N];
queue<PII> q;
void init() {
memset(vis, false, sizeof(vis));
}
void bfs() {
q.push({n, 0});
vis[n] = true;
while(q.size()) {
int a = q.front().first * 2;
int b = q.front().first - 1;
int step = q.front().second;
q.pop();
if (a == m || b == m) {
cout << step + 1 << endl;
return;
}
if (!vis[a] && a >= 1 && a <= N) {
vis[a] = true;
q.push({a, step + 1});
}
if (!vis[b] && b >= 1 && b <= N) {
vis[b] = true;
q.push({b, step + 1});
}
}
}
void solve() {
cin >> n >> m;
init();
if (n == m) {
cout << 0 << endl;
return;
}
if (n > m) {
cout << n - m << endl;
return;
}
bfs();
}
int main() {
solve();
return 0;
}
三、deque
介绍:
双向队列。上一个的队列,只能够删除队首,在队尾插入。而双向队列,能够支持队首和队尾删除和插入的操作。
常用于滑动窗口等问题。
使用的时候需要加上头文件:
#include<deque>
初始化:
deque<int> q;
函数调用:
q.push_back(); q.push_front(); q.size(); q.front(); q.back(); q.pop_back(); q.pop_front(); q.empty(); 的时间复杂度均为\(O(1)\)。
deque<int> q;
q.push_back(50); //在队列尾巴插入元素 50
q.push_back(666); //在队列尾巴插入元素 666
q.push_front(10); //在队列头部插入元素 10
q.push_back(5201314); //在队列尾巴插入元素 5201314
q.push_back(2333); //在队列尾巴插入元素 2333
cout << q.size() << endl; //返回双向队列中的元素个数。
//输出结果为 5
for (int i = 0; i < q.size(); i++) cout << q[i] << " \n"[i == q.size() - 1];
//输出结果为 10 50 666 5201314 2333
cout << q.front() << " " << q.back() << endl;
//返回队列头部元素 和 返回队列尾巴元素
//输出结果为 10 2333
q.pop_front(); q.pop_back();
//删除队首元素 和 删除队尾元素
for (int i = 0; i < q.size(); i++) cout << q[i] << " \n"[i == q.size() - 1];
//输出结果为 50 666 5201314
if (q.empty()) cout << "空" << " ";
else cout << "非空" << " ";
q.clear(); //清空双向队列
if (q.empty()) cout << "空" << endl;
else cout << "非空" << endl;
//如果是空双向队列,则返回值为真。
//如果双向队列中含有元素,则返回值为假。
//输出结果为 非空 空
q.push_back(10); q.push_back(20); q.push_back(30); q.push_back(40);
q.push_back(50); q.push_back(60); q.push_back(70); q.push_back(80);
for (int i = 0; i < q.size(); i++) cout << q[i] << " \n"[i == q.size() - 1];
//输出结果为 10 20 30 40 50 60 70 80
q.erase(q.begin() + 2);
//删除迭代器所在的元素
for (int i = 0; i < q.size(); i++) cout << q[i] << " \n"[i == q.size() - 1];
//输出结果为 10 20 40 50 60 70 80
q.erase(q.begin() + 3, q.begin() + 6);
//删除两个迭代器之间的元素,包括前面的那个元素,不包括后面的那个元素。
for (int i = 0; i < q.size(); i++) cout << q[i] << " \n"[i == q.size() - 1];
//输出结果为 10 20 40 80
例题:滑动窗口
acwing 154. 滑动窗口
原题指路:154. 滑动窗口 - AcWing题库
洛谷 P1886 滑动窗口 /【模板】单调队列
原题指路:P1886 滑动窗口 /【模板】单调队列 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
代码一:(模拟版)
#include<bits/stdc++.h>
using namespace std;
//求滑动窗口中的最小值的时候,只要把所有逆序的数字删掉
//那么队列中留下的就一定是一个单调上升的队列。
//原因:左边的数字先出去,左边的这个数字还比右边的某个数字大,那么这个左边的数字就没有输出的可能性
const int N = 1e6 + 10;
int n, k;
int a[N]; //输出的数字串
int q[N]; //单调队列,存的是下标
void solve() {
cin >> n >> k;
for (int i = 0; i < n; i++) {
cin >> a[i];
}
int hh = 0; int tt = -1; //定义队头和队尾。
for (int i = 0; i < n; i++) {
//判断队头是否已经滑出窗口。
//队列里存的其实是下标。
if (hh <= tt && i - k + 1 > q[hh]) hh++; //队尾往前走一步,如果能走的话
while (hh <= tt && a[q[tt]] >= a[i]) tt--; //如果队列中的数比新的数大的话就可以走了。
q[++tt] = i; //新加入来的一个数。
if (i >= k - 1) cout << a[q[hh]] << " ";
}
cout << endl;
hh = 0, tt = -1;
for (int i = 0; i < n; i++) {
if (hh <= tt && i - k + 1 > q[hh]) hh++;
while (hh <= tt && a[q[tt]] <= a[i]) tt--;
q[++tt] = i;
if (i >= k - 1) cout << a[q[hh]] << " ";
}
}
int main() {
solve();
return 0;
}
代码二:(双向队列版)
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int a[N];
int n, k;
void solve() {
cin >> n >> k;
for (int i = 0; i < n; i++) {
cin >> a[i];
}
deque<int> q;
for (int i = 0; i < n; i++) {
if (!q.empty() && i - k + 1 > q.front()) q.pop_front();
while(q.size() && a[q.back()] >= a[i]) q.pop_back();
q.push_back(i);
if (i >= k - 1) cout << a[q.front()] << " ";
}
cout << endl;
q.clear();
for (int i = 0; i < n; i++) {
if (!q.empty() && i - k + 1 > q.front()) q.pop_front();
while(q.size() && a[q.back()] <= a[i]) q.pop_back();
q.push_back(i);
if (i >= k - 1) cout << a[q.front()] << " ";
}
}
int main() {
solve();
return 0;
}
四、priority_queue
介绍:
优先队列除了和常规队列一样数据先进先出以外,优先队列中的元素还被赋予了优先级,并且保证了队首的元素是优先级最高的。它是通过堆来实现优先级这一功能的。
常用于堆优化版dijkstra算法,动态中位数,一些模拟题当中。
使用的时候需要加上头文件:
#include<queue>
初始化:
普通优先队列初始化:
priority_queue<int> q1; //默认是大根堆,也就是说队首的元素是最大值
priority_queue<int, vector<int>, less<int>> q2; //大根堆,和上面那个一样
priority_queue<int, vector<int>, greater<int>> q3; //小根堆,队首元素是最小值。
//构造的时候,第一个元素表示数据类型,第二个元素是容器,第三个元素是优先级。
结构体的优先队列初始化:
其中要注意的是,优先队列的“排序”方式和sort不太一样,按照下方代码中的cmp函数,如果是sort的话,那么就是从小到大排序,但是在优先队列中的话就是由大到小排序。也就是说,下面的排序方式是,先看node中的x,再看y。x大的排在前面,x相同的话那么y大的排在前面。
struct node{
int x, y;
};
struct cmp {
bool operator() (const node &a, const node &b) {
if (a.x != b.x) return a.x < b.x;
return a.y < b.y;
}
};
void solve() {
priority_queue<node, vector<node>, cmp> q;
}
pair类型的优先队列初始化:
默认构造是按照先比较pair中的first元素,将first元素大的放前面,然后再比较second元素,将second元素大的放前面。
priority_queue<pair<int, int>> q;
q.push({1, 4});
q.push({5, 2});
q.push({1, 2});
q.push({1, 3});
q.push({3, 3});
while(q.size()) {
cout << q.top().first << " " << q.top().second << endl;
q.pop();
}
/*
输出结果为:
5 2
3 3
1 4
1 3
1 2
*/
函数调用:
priority_queue<int> q;
q.push(5); //在双向队列中插入元素 5
q.push(1); q.push(7); q.push(8); q.push(666);
cout << q.size() << endl; //返回优先队列中元素个数
if (!q.empty()) cout << "非空" << endl; //查询优先队列是否为空。
while(q.size()) {
cout << q.top() << " "; //返回队首元素。
q.pop(); //删除队首元素。
}
/*
输出结果为:
5
非空
666 8 7 5 1
*/
例题:
①:acwing 850. Dijkstra求最短路 II
原题指路:850. Dijkstra求最短路 II - AcWing题库
代码:
#include<bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;
//堆优化版本的dijkstra算法
//也就是在朴素的版本上进行了修改
//可以选择用手写堆来优化,能保证堆里面的元素个数是正好n个,但是就是会麻烦一点。时间复杂度mlogn
//也可以用C++里面的优先队列,但是由于不支持修改任意一个操作,所以每次修改都只能往里面插入新的数
//所以优先队列里面的元素就可能会有m个,但是就是会方便很多
//举个例子就是两个点的距离可能会存在两个数。时间复杂度mlogm ≈ mlogn (∵m<n^2,logm^2=2logm)
//堆优化版本的dijkstra适用于稀疏图
//用邻接表的话,就不需要对重边进行处理了,因为算法他本身就会把这个事情处理掉了。
//用堆来维护所有点的这个距离,维护距离的时候还需要知道结点的编号是多少,所以堆里面存的其实是一个pair
const int INF = 0x3f3f3f3f;
const int N = 1e6 + 10;
int n, m;
int h[N], w[N], e[N], ne[N], idx; //改成邻接矩阵的形式
int dist[N]; //表示从i号点走到起点的当前的最短路的距离是多少
bool st[N]; //表示每个点的最短路的距离是不是已经确定了
void add(int a, int b, int c) {
e[idx] = b; w[idx] = c, ne[idx] = h[a]; h[a] = idx++;
}
int dijkstra() {
memset(dist, 0x3f, sizeof(dist));
dist[1] = 0;
priority_queue<PII, vector<PII>, greater<PII>> heap; //一维数组用vector的话就比较方便,greater是参数,这么写的话就会变成小根堆。
heap.push({0, 1}); //上来先把一号点放进去,因为一号点是已经知道最短路的距离的了
while (heap.size()) { //保证队列里面不空的
auto t = heap.top(); //每次找到距离最小的点就是堆的起点
heap.pop();
int ver = t.second; //表示这个点的点编号
int distance = t.first; //表示这个点的距离
if (st[ver]) continue; //如果这个点已经处理过了,说明他是冗余备份,就不用再处理它了
st[ver] = true;
for (int i = h[ver]; i != -1; i = ne[i]) { //用当前这个点的距离来更新其他点的这个最短距离。
int j = e[i];
if (dist[j] > distance + w[i]) {
dist[j] = distance + w[i];
heap.push({dist[j], j});
}
}
}
if (dist[n] == INF) return -1; //假设不连通的话,就返回-1
return dist[n]; //不然的话,就返回最短路的距离
}
void solve() {
cin >> n >> m;
memset(h, -1, sizeof(h)); //初始化邻接表
while (m--) {
int a, b, c;
cin >> a >> b >> c;
add(a, b, c);
}
int t = dijkstra();
cout << t << endl;
}
int main() {
solve();
return 0;
}
②:洛谷 P1168 中位数
原题指路:P1168 中位数 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
代码:
#include<bits/stdc++.h>
using namespace std;
//用一个漏斗堆来维护
//上面是一个(倒着的)小根堆,存的是大的那一半元素
//下面是一个(正着的)大根堆,存的是小的那一半元素
//保证插入偶数个数字之后,两个堆内的元素个数数量相同
//插入奇数个数字的时候,下面的堆比上面的那个堆多一个元素
//那么中间的那个点,就是中位数,他小于上面的那一半元素,大于下面的那一半元素
void solve() {
int n; cin >> n;
priority_queue<int> down;
priority_queue<int, vector<int>, greater<int>> up;
for (int i = 1; i <= n; i++) {
int x; cin >> x;
if (down.empty() || x <= down.top()) down.push(x);
else up.push(x);
if (down.size() > up.size() + 1) up.push(down.top()), down.pop();
if (up.size() > down.size()) down.push(up.top()), up.pop();
if (i % 2) {
cout << down.top() << endl;
}
}
}
int main() {
solve();
return 0;
}
③:Codeforces Round 840 (Div. 2) and Enigma 2022 - Cybros LNMIIT B
代码:
#include<bits/stdc++.h>
using namespace std;
#define CaseT int CaseT; cin >> CaseT; while(CaseT--)
typedef pair<int, int> PII;
const int INF = 0x3f3f3f3f;
const int N = 1e5 + 10;
int n, k;
bool st[N];
//用小根堆来维护
//那么一开始的时候,第一回合是打k点伤害,打到不能打死怪物为止
//打完后把死掉的怪物删掉
//打完之后,攻击力减少小根堆的堆顶
//那么第二轮再打的时候,攻击力就是k + (k - top)
//以此类推,那么就不用像一开始那样,通过模拟一遍遍的扫,然后一次次的降低每个怪物的血量
//而是变成提升每轮的攻击力,来判断能不能往后继续进攻。
//所以k就有了两个作用,一个是用来每轮增加变成攻击力,一个是每轮减少,计算一共减少的攻击力,充当生命值的作用。
void solve() {
memset(st, false, sizeof(st));
cin >> n >> k;
PII mon[n];
for (int i = 0; i < n; i++) cin >> mon[i].first;
for (int i = 0; i < n; i++) cin >> mon[i].second;
sort(mon, mon + n);
priority_queue<PII, vector<PII>, greater<PII>> q; //小根堆维护怪物的攻击力
for (int i = 0; i < n; i++) q.push({mon[i].second, i});
int temp = k; //temp表示当前的攻击力
bool fight = true; //能不能打赢
int j = 0;
while(1) {
while(j < n && temp >= mon[j].first) st[j++] = true; //如果当前攻击力大于怪物血量,那么就说明能打掉这个怪物
if (j >= n) break; //怪物全部打掉之后就退出循环
while(st[q.top().second]) q.pop(); //如果这个怪物被打掉了,那么就删掉他
if (k - q.top().first <= 0) { //到怪物的回合了,炮台生命值减去小根堆的堆顶
fight = false; //炮台被干掉了,战斗失败
break;
}
temp += k - q.top().first; //炮台的攻击力增加一轮的输出并减去减少的伤害值
k -= q.top().first; //炮台的生命值减去怪物伤害的最小值
}
if (fight) cout << "YES" << endl;
else cout << "NO" << endl;
}
int main() {
CaseT
solve();
return 0;
}
五、stack
介绍:
栈就像是一个瓶子一样的容器,最先进去的数据被压在瓶子底部最后出来,最后进去的数据放在瓶子的表面最先出来。因此栈是先进后出,后进先出。
常用于单调栈等数据结构。
使用的时候需要加上头文件:
#include<stack>
函数调用:
stack<int> s;
s.push(10); //插入元素 10;
s.push(20); s.push(30); s.push(40); s.push(50);
cout << s.top() << endl;
//返回栈顶元素,输出结果为 50
s.pop(); //删除栈顶元素。
cout << s.top() << endl;
//输出结果为 40
cout << s.size() << endl;
//返回栈中的元素个数,输出结果为 4
if (s.empty()) cout << "空" << endl;
else cout << "非空" << endl;
//查询栈是否为空。输出结果为 非空
例题:acwing 830. 单调栈
原题指路:830. 单调栈 - AcWing题库
代码一:(模拟版)
#include<bits/stdc++.h>
using namespace std;
//题目:给出一串数字,然后输出每个数字的左边第一个小于它的数字大小(思路:用单调栈滑动窗口)
const int N = 1e5 + 10;
int stk[N];
int tt; //栈中的元素个数。
void solve() { //先进的后出,后进的先出来。
int n; cin >> n;
for (int i = 0; i < n; i++) {
int x; cin >> x;
while(tt && stk[tt] >= x) tt--; //如果说这个栈不为空,而且栈顶的数大于x,那么这个数就再也不会用到了。
if (tt) cout << stk[tt] << " ";
else cout << -1 << " ";
stk[++tt] = x;
}
}
int main() {
solve();
return 0;
}
代码二:(栈版)
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
void solve() {
int n; cin >> n;
stack<int> s;
for (int i = 0; i < n; i++) {
int x; cin >> x;
while(!s.empty() && s.top() >= x) s.pop();
if (s.size()) cout << s.top() << " ";
else cout << -1 << " ";
s.push(x);
}
}
int main() {
solve();
return 0;
}
六、string
介绍:
可以像int, double类型一样,理解为字符串型,和char的字符串差不多。
使用的时候需要加上头文件:
#include<string>
初始化:
string s1; //初始化空字符串
string s2 = "Autumn_goose"; //初始化这一字符串
string s3("233333"); //和上面那个一样
string s4(5, '6'); //相当于初始化一个 含有5个'6'这一字符 的字符串
string s5(s2, 5); //复制s2字符串从第5个(因为下标从0开始所以实际上是第6个)开始到最后一个字符
cout << s4 << " " << s5 << endl;
//输出结果为: 66666 n_goose
语法使用:
一个字符串中单个字符的访问:
string s = "abcdef";
for (int i = 0; i < 6; i++) cout << s[i] << " ";
//输出结果为 a b c d e f
字符串数组的创建以及单个字符的访问:
int n; string s[n];
for (int i = 0; i < n; i++) {
for (int j = 0; j < s[i].length(); j++) {
cout << s[i][j];
}
}
字符串的相加:
相当于直接在一个字符串后面再连接一个字符串。
string s1 = "qiuyan";
string s2 = "shishuaige";
string s3 = s1 + s2;
cout << s3 << endl;
//输出结果为 qiuyanshishuaige
字符串的比较:
string s1 = "aa";
string s2 = "aaa";
string s3 = "abbbb";
string s4 = "bbb";
string s5 = "123";
string s6 = "111";
if (s1 < s2) cout << "s1 < s2" << endl;
if (s1 < s3) cout << "s1 > s3" << endl;
if (s2 < s4) cout << "s2 < s4" << endl;
//由前三个比较可知,字符串比较是从前到后挨个字符比较,直到两个字符不相同时(或其中一个已经没了),字典序大的(或者字符串长度长的)字符串更大。
if (s5 > s6) cout << "s5 > s6" << endl;
if (s4 > s5) cout << "s4 > s5" << endl;
//不只是字母可以比较,数字和数字之间,数字和字母之间等等都可以比较。按照字典序进行排序。
函数调用:
string s = "qiuyan666";
cout << s.length() << " " << s.size() << endl;
//返回该字符串的长度 9 ,两个函数是差不多一样的。
s.push_back('A'); //在字符串的最后面加上一个字符'A'。
cout << s << endl; //qiuyan666A
s.insert(s.begin() + 5, 'n'); //在字符串的某个位置加上字符
cout << s << endl; //qiuyann666A
s.append("handsome"); //在字符串的后面再加上一个字符串
// 相当于 s += "handsome"; 的操作。
cout << s << endl; //qiuyann666Ahandsome
s.erase(s.begin() + 10); //删除字符串中某个位置的字符。
cout << s << endl; //qiuyann666handsome
s.erase(6, 4); //删除这个字符串从第6位开始往后长度为4的字符。
cout << s << endl; //qiuyanhandsome
s.erase(s.begin() + 3, s.begin() + 6);
//删除字符串某个区间上面的字符,包括前面的,不包括后面的。
cout << s << endl; //qiuhandsome
cout << s.substr(3, 6) << endl; //handso
//截取字符串中从第3未开始长度为6的子串
cout << s.find("hand", 1) << endl; //从第1个字符开始向后查找"hand"这个字符串
//如果找到了,返回第一个找到的该字符串的首个字符的位置 3
cout << s.find("fsffajog", 1) << endl;
//如果没找到会输出18446744073709551615,也就是 2 ^ 64 - 1
string a = "hhahhahhahhhahhahha";
cout << a.find("hha") << endl; //只返回找到的第一个字符串的首字母下标 0
cout << s.find('n', 0) << endl; //从下标为0的字符开始往后寻找字符串中是否存在'n'这个字符。
//如果找到了就输出第一个找到的该字符的下标。
cout << s.rfind("hand", 9) << endl; //从后往前遍历查找该字符串第一次出现的首字符的下标 3
cout << a.rfind("hha", 18) << endl; //输出结果为16
cout << s.rfind('n', 9) << endl; //从后往前遍历找该字符
排序:
string s = "sgakfhashh";
sort(s.begin(), s.end());
cout << s << endl;
//输出结果为 aafghhhkss
七、map
介绍:
map就像是函数中的映射关系一样,每个键都会对应一个值。或者也可以理解为名字和学号之间的关系,每一个名字都会对应一个学号。比如 秋雁 对应 666。 春雁 对应 233。同时,map会根据键的顺序从小到大自动排序。
使用的时候需要加上头文件:
#include<map>
初始化:
map<int, int> m1;
map<string, int> m2;
struct node {};
map<string, node> m3;
函数调用:
插入与删除:
map<string, int> m;
m["xiayan"] = 520;
m["qiuyan"] = 666;
m.insert({"chunyan", 233});
m.insert({"dongyan", 1314});
访问方式:
cout << m["qiuyan"] << endl; //直接访问某个键的值
for (auto i : m) { //智能指针访问
cout << i.first << " " << i.second << endl;
}
map<string, int>::iterator it; //迭代器访问
for (it = m.begin(); it != m.end(); it++) {
cout << it->first << " " << it->second << endl;
}
for (auto [x, y] : m) { //注:c++17以后版本才有这高端玩意。
cout << x << " " << y << endl;
}
常用函数:
m.size(); m.erase(it) 的时间复杂度为\(O(1)\)。 m.clear() 的时间复杂度为\(O(n)\)。
m.erase(key) 的时间复杂度为\(O(logN)\);
cout << m.size() << endl;
//返回map中有几对键值。输出结果为 4
map<string, int>::iterator it1 = m.find("qiuyan");
cout << it1->first << " " << it1->second << endl;
//如果这个数据存在,则返回这个数据所在迭代器。
cout << m.count("qiuyan") << " " << m.count("bucunzai") << endl;
//如果存在的话就返回1,不存在的话就返回0。
m.erase(it1); //删除这个迭代器对应的一对键和值
m.erase("chunyan"); //删除这个键和其所对应的值。
cout << m.size() << endl; //输出结果为2
m.clear(); //清空map
cout << m.size() << endl; //输出结果为0
m["huang"] = 5; m["hao"] = 8; m["zi"] = 24;
m["pi"] = 12; m["ka"] = 1; m["qiu"] = 10;
for (auto i : m) { //智能指针访问
cout << i.first << " " << i.second << endl;
}
/*
输出结果为:
hao 8
huang 5
ka 1
pi 12
qiu 10
zi 24
*/
map<string, int>::iterator it2 = m.find("ka");
map<string, int>::iterator it3 = m.find("zi");
m.erase(it2, it3); //删除一个区间内的键值
for (auto i : m) { //智能指针访问
cout << i.first << " " << i.second << endl;
}
/*
输出结果为:
hao 8
huang 5
zi 24
*/
if (m.empty()) cout << "空" << endl;
else cout << "非空" << endl;
//查询map是否为空。输出结果为 非空
例题:
①:2022 Jiangsu Collegiate Programming Contest A
代码:
#include<bits/stdc++.h>
using namespace std;
void solve() {
int n; cin >> n;
map<string, vector<string>> kill;
for (int i = 1; i <= n; i++) {
string a, b; cin >> a >> b;
kill[a].push_back(b);
}
for (auto i : kill) {
for (int j = 0; j + 5 <= i.second.size(); j++) {
if (set<string>(i.second.begin() + j, i.second.begin() + j + 5).size() == 5) {
cout << "PENTA KILL!" << endl;
return;
}
}
}
cout << "SAD:(" << endl;
}
int main() {
solve();
return 0;
}
②:洛谷 P2580 于是他错误的点名开始了
原题指路:P2580 于是他错误的点名开始了 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
代码:
#include<bits/stdc++.h>
using namespace std;
map<string, int> a;
void solve() {
int n; cin >> n;
while(n--) {
string s; cin >> s;
a[s] = 1;
}
int m; cin >> m;
while(m--) {
string s; cin >> s;
if (a[s] == 1) {
cout << "OK" << endl;
a[s] = 2;
}
else if (a[s] == 2) {
cout << "REPEAT" << endl;
}
else cout << "WRONG" << endl;
}
}
int main() {
solve();
return 0;
}
③:Codeforces Round 847 (Div. 3) D
代码:
#include<bits/stdc++.h>
using namespace std;
#define CaseT int CaseT; cin >> CaseT; while(CaseT--)
void solve() {
int n; cin >> n;
vector<int> a(n);
map<int, int> bin; //bin[i]的值表示i这个数字出现了多少次
for (int i = 0; i < n; i++) {
cin >> a[i];
bin[a[i]]++;
}
sort(a.begin(), a.end());
int ans = 0;
for (int i = 0; i < n; i++) {
if (bin[a[i]]) {
ans++;
for (int k = a[i]; bin[k] != 0; k++) {
bin[k]--;
}
}
}
cout << ans << endl;
}
int main() {
CaseT
solve();
return 0;
}
八、set
介绍:
set是一个集合,里面存放的元素都不相同。当集合里面已经有某个元素,再进行插入操作的时候,就不会再插入这个元素了。同时set里面的元素会自动从大到小进行排序。用一句话来说就是set里面的元素有序且不重复。
使用的时候需要加上头文件:
#include<set>
函数调用:
插入与删除:
s.size() 的时间复杂度为\(O(1)\)。
set<int> s;
s.insert(5); //往set里面插入元素 5
s.insert(100);
cout << s.size() << endl;
//返回set中的元素个数 2
s.insert(5);
cout << s.size() << endl;
//输出结果仍然为 2。可知插入重复元素的时候是不会继续往set里面插入的
访问方式:
for (auto i : s) { //智能指针访问
cout << i << " ";
}
cout << endl;
//输出结果为 -1 0 1 3 5 10 100 233 555 666
set<int>::iterator it1; //迭代器访问
for (it1 = s.begin(); it1 != s.end(); it1++) {
cout << *it1 << " ";
}
cout << endl;
常用函数:
s.clear() 的时间复杂度为\(O(n)\)。
set<int>::iterator it2 = s.find(666);
cout << *it2 << endl;
//查找某一元素,如果存在的话就返回该元素的迭代器。
cout << s.count(555) << endl;
cout << s.count(111) << endl;
//查找某一元素是否出现,出现的话输出1,没出现的话输出0。
s.erase(it2); //删除这个迭代器所对应的元素
s.erase(3); //直接删除这个元素
for (auto i : s) { //智能指针访问
cout << i << " ";
}
cout << endl;
//输出结果为 -1 0 1 5 10 100 233 555
set<int>::iterator it3 = s.find(5);
set<int>::iterator it4 = s.find(233);
s.erase(it3, it4); //删除这个区间的元素。
for (auto i : s) {
cout << i << " ";
}
cout << endl;
//输出结果为 -1 0 1 233 555
if (s.empty()) cout << "空" << endl;
else cout << "非空" << endl;
//查询集合是否为空。输出结果为 非空
s.clear(); //清空集合
if (s.empty()) cout << "空" << endl;
else cout << "非空" << endl;
//输出结果为 空
改变排序规则:
//方法一:变成从大到小排
set<int> s1; //默认从小到大排序
set<int, less<int>> s2; //和上面那个是一样的
set<int, greater<int>> s3; //从大到小排序
//方法二:重载运算符
struct cmp {
bool operator() (const int &x, const int &y) const {
return x > y;
}
};
set<int, cmp> s4;
//方法三:结构体比较
struct node {
int x; int y;
bool operator < (const node &a) const {
if (x != a.x) return x < a.x;
return y < a.y;
}
};
set<node> s5;
例题:
①:Pinely Round 1 (Div. 1 + Div. 2) B
原题指路:Problem - 1761B - Codeforces
代码:
#include<bits/stdc++.h>
using namespace std;
#define CaseT int CaseT; cin >> CaseT; while(CaseT--)
const int N = 110;
int n;
int a[N];
void solve() {
int n; cin >> n;
set<int> s;
for (int i = 0; i < n; i++) {
int a; cin >> a;
s.insert(a);
}
cout << (s.size() > 2 ? n : n / 2 + 1) << endl;
}
int main() {
CaseT
solve();
return 0;
}
②:Codeforces Round 620 (Div. 2) B
原题指路:Problem - 1304B - Codeforces
代码:
#include<bits/stdc++.h>
using namespace std;
#define CaseT int CaseT; cin >> CaseT; while(CaseT--)
#define all(x) (x).begin(), (x).end()
//组成一个新的长的回文串只能有两种情况
//一种是本身构成回文串,然后放在新得到串的中间
//另一种就是和另一个串正好是反过来的,一左一右放两边
set<string> st;
void solve() {
int n, m; cin >> n >> m;
string s, t, mid, ans="", rev="";
while(n--) {
cin >> s;
t = s; //t复制一下字符串,用来反转
reverse (all(t));
if (s == t) mid = t; //如果本身回文就放中间,不用考虑
else if (st.count(t)) { //查询这个字符串是否有出现
ans += t; //如果有和反转后相等的,就把反转存前面
rev = s + rev; //然后这个字符串放后面。
}
st.insert(s); //将这个字符串存进集合中
}
ans = ans + mid + rev;
cout << ans.size() << endl << ans << endl;
}
int main() {
solve();
return 0;
}
九、sort
普通数组的排序方式:
const int N = 1e6 + 10;
int a[N];
void solve() {
int n;
sort(a, a + n); //默认将 a数组中 0 ~ n - 1 上的数从小到大排列。
sort(a + 1, a + n + 1); //将 a数组中 1 ~ n 上的数从小到大排列。
sort(a, a + n, greater<int>()); //从大到小排列。
sort(a, a + n, less<int>()); //从小到大排列。
}
vector数组的排序方式:
int n;
vector<int> a(n);
sort(a.begin(), a.end());
结构体的排序方式:
const int N = 1e6 + 10;
struct node {
int x, y, z;
}a[N];
bool cmp (const node &a, const node &b) {
if (a.x != b.x) return a.x < b.x;
if (a.y != b.y) return a.y < b.y;
return a.z < b.z;
}
void solve() {
int n;
sort(a, a + n, cmp);
}
十、pair
介绍:
可以将pair看成是只有两个元素的结构体。
初始化与赋值:
pair<int, int> p1;
pair<int, string> p2;
pair<pair<int, int>, int> p3;
pair<pair<int, int>, pair<int, int>> p4;
pair<string, int> p5("qiuyan", 666);
p1 = {520, 1314};
p2 = make_pair(123, "mutouren");
p3 = pair<pair<int, int>, int>(pair<int, int>(1, 2), 3);
p4.first.first = 1; p4.first.second = 2; p4.second.first = 3; p4.second.second = 4;
访问方式:
int n;
pair<int, int> p[n];
for (int i = 0; i < n; i++) {
cin >> p[i].first >> p[i].second;
cout << p[i].first << " " << p[i].second << endl;
}