仍在施工中

介绍C++ STL的一些函数和容器的用法。

简介

C++ Standard Template Library(标准模板库),简称 STL

成员

容器

map

有建立映射的关系。对于每一个 $key$ 有一个 $value$ 与之对应。

$$ key -> value $$

时间复杂度 $ O(log\_n) $

使用前包含头文件 <map>

#include

比如我们新建了一个 mapm,$key$ 是 string 类型,$key$ 是 int 类型,可以这么写:

map m;

m.find(x):传入 $key$。如果找到返回迭代器;否则返回 m.end()。由此可以发现,m.find(x)!=m.end() 可以判断 m[x] 是否存在

m.erase(x):删除 m[x]

m.clear():清空 m

如何遍历 map 中的元素?

for (auto it = m.begin(); it != m.end(); it++) {
string id = it->first;
int val = it->second;
cout << id << ':' << val << el;
}

其中的 auto 会自动获取类型。在此样例中等价于map <string, int>::iterator

注:auto 需要在 C++11 下使用。正式赛中也可使用

#include<iostream>
#include<map>
#define el endl
using namespace std;
int main() {
ios::sync\_with\_stdio(false);
cin.tie(nullptr);
map <string, int>m;
m["aaa"] = 999;
m["bbb"] = 8;
m["uuu"] = 888;
m["haha"] = 888;
m["o"] = 23456;
//查询
bool find\_bbb = m.find("bbb") != m.end();
bool find\_ccc = m.find("ccc") != m.end();
cout << find\_bbb << ' ' << find\_ccc << el;
//1 0
//删除
bool find\_o\_before = m.find("o") != m.end();
m.erase("o");
bool find\_o\_after = m.find("o") != m.end();
cout << find\_o\_before << ' ' << find\_o\_after << el;
//1 0
//元素数量
cout << m.size() << el;
//4
//遍历
for (auto it = m.begin(); it != m.end(); it++) {
string id = it->first;
int val = it->second;
cout << id << ':' << val << el;
}
/\*
aaa:999
bbb:8
haha:888
uuu:888
\*/
return 0;
}

unordered\_map

map类似,但底层是通过Hash实现的,打CF比赛不要用,由于它的Hash模数是固定的所以容易被Hack。它的时间复杂度为 $ O(1) $

set

用以维护一个不可重集合

头文件<set>

#include

定义时:

set s;

会定义一个名叫s的、存储元素类型为int的集合

map类似,也有finderasesizeclear函数

遍历元素:和 map 一样,也可以使用迭代器来遍历元素:

for (auto it=s.begin();it!=s.end();it++)
cout << \*it << " ";

set 会自动对其中的元素做升序排列并去重

我觉得这样更方便:

for (int val : s)
cout << val << " ";

setmap 一样,依赖与二叉平衡树。时间复杂度 $ O(log\_n) $

如果我们希望维护一个可重的集合如 $A=\{1,1,2,3\}$,可以使用 multiset 来实现

multiset

multiset 可以维护一个可重的集合

它也包含在 <set> 中。

m.lower_bound(val):返回小于等于 val 中最大数的地址(-1就是 val前驱

m.upper_bound(val):返回 val后继

由于 multiset 可重的特性,删除元素有两种情况

  • 删除所有值为 x 的元素
m.erase(x);
  • 删除一个值为 x 的元素
if (m.find(x) != m.end())
m.erase(m.find(x));

注意判断 x 是否存在

priority\_set

优先队列。

给一个序列插入元素并及时获取最大值最小值

以 $O(log\_n)$ 插入元素;$O(1)$ 获得最大/小值

头文件:<queue>

priority\_queue q;

建立名为 q 的优先队列,存储元素类型为 int

priority\_queue , greater >q;

同上,但这让队首从最大值变为最小值

q.top():获取队首

q.pop():弹出队首

q.size():获取大小

q.push(x):将 x 插入优先队列

优先队列不提供clear

这里有一道优先队列的例题:P3378 【模板】堆

函数

sort

传入排序的开始和结束的下一个地址。

排序类似 vectorstring的容器排序时:调用

sort(s.begin(), s.end());

unique

(英文单词独特)

有序数组进行去重的功能

假设我们有:

$$ a\_1=1,a\_2=1,a+3=2,a\_4=2 $$

要对 $a\_1$ ~ $a\_4$ 去重,调用 unique(a+1,a+1+4)即可

unique(a+1,a+1+4)会返回

reverse

翻转。

传入翻转的开始和结束的下一个地址。同sort,如果是string等,使用

reverse(s.begin(), s.end());

会修改以前的数组


字符串Hash,KMP,字典树的一些笔记

字符串Hash

这是什么

一个可以将任意长度的字符串映射为一个非负整数的算法。即,不同的字符串映射出不同的值,相同的映射出相同的值。

原理

将字符串视作一个 $ P $ 进制的数,对于字符串中的每个字符分配一个数值

字符集是字符串中有可能出现的字符的一个集合,如,小写字母的字符集为 $ \{a, b, c, d, ..., z\} $

同样以小写字母为例,分配 $ a=1, b=2, ...$

一般情况下,将 $P$ 设为 $13331$ 即可

但如果串很长($10^5$) 就会超出 int 类型的范围。碰到这种情况,可以使用 unsigned long long int 存储Hash值,它会自动对数值进行取模,比手动取模快不少

但这样一来,就可能出现Hash冲突,请设想这样一种情况:A字符串的Hash为$ h $,B字符串的Hash为 $ h + $ 模数,那么它们取模后的Hash值是一样的,怎么办呢?

可以多模:用多个模数同时模字符串的Hash,但模数的数量不要超过2个,否则容易TLE!

几个对字符串的操作对Hash值的影响:

插入单个字符

对字符串 $ S $ 插入一个字符 $ C $:($ H $ 指字符串的Hash值,$ V $指给字符分配的数值,下同)

$$ H(S+C)=H(S)\*P+V(C) $$

两个字符串相减

已知字符串 $ S+T $ 、 $ S $ 的Hash值,$ T $ 的Hash值:($ K $为 $ T $的长度)

$$ H(T)=H(S+T)-H(S)\*P ^ K $$

(预处理 $ P $ 的若干次方!)

前缀和

由前面可知,字符串的Hash值具有可加和可减性,由此可以使用前缀和来处理字符串Hash值。

时间复杂度:以 $O(K)$ 的时间复杂度来处理 $ S $的每个前缀Hash值;以 $O(1)$ 的时间复杂度查询任意长度字串的Hash值

代码

获取字符串Hash值的函数:(不要用hash做函数名!)

int ahash(string s) {
int h = 0;
for (int i = 0; i < s.size(); i++) {
h = h \* 27 + (s[i] - 'a' + 1);
}
return h;
}

KMP

待补充


字典树

这是什么

一类 $ K $ 叉树,用于字符串的快速检索

原理

当要插入一个字符串 $ S $ 时,先将 $ R $ 置为 $ K $ 叉树的根节点上,对 $ S $ 中的每一个字符执行以下操作:

如果 $ R $ 上的 $ S\_i $ 为空,则在 $ R $ 的 $ S\_i $ 边新建一个节点并将 $ R $ 置于新建的节点上;否则将 $ R $ 移动过去

结束后,在 $ R $ 上写入一个结束标志,完成!

代码

(这是P8306的代码)

你别问我为什么要用指针做!

#include
#define el endl
#define ll long long
using namespace std;
struct node {
node \*z[63] = {nullptr};
int count = 0;
};
int id(char s) {
if (s <= 'Z' && s >= 'A') return s - 'A' + 1;
if (s <= 'z' && s >= 'a') return s - 'a' + 1 + 26;
return s - '0' + 52 + 1;
}
// 获取单个字符的编号
void s() {
node \*root = new node;
int a, b;
cin >> a >> b;
for (int i = 0; i < a; i++) {
string s;
cin >> s;
node \*r = root;
for (int j = 0; j < s.size(); j++) {
int ii = id(s[j]);
if (r->z[ii] == nullptr) r->z[ii] = new node;
r = r->z[ii];
r->count++;
}
}
// 输入
for (int pp = 0; pp < b; pp++) {
string s;
cin >> s;
node \*r = root;
bool flag = false;
for (int j = 0; j < s.size(); j++) {
int ii = id(s[j]);
if (r->z[ii] == nullptr) {
flag = true;
break;
}
r = r->z[ii];
}
if (!flag) cout << r->count << el;
else cout << 0 << el;
}
// 查询
}
int main() {
ios::sync\_with\_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
while (n--) {
s();
}
return 0;
}

话说这题调了好久,发现是第24行的 $ j $ 写成了 $ i $...


洛谷CF1759B的一种奇妙的解法

一种奇妙的解法

题意

有一个数列是 $1 \sim n$ 的一种排列。丢掉了几个数,给出丢掉数的和留着的数,问它们是否能组成这个数列?

思路

我们都知道,求这样公差为 $1$ 的等差数列的和公式是
$$\dfrac{n(n+1)}{2}$$

而根据输入的数据我们可以把数列和求出来,$\times2$ 再平方根即可求出 $n$ !

最后确保 $n \geq$ 留着的数中最大的数并且用 $n$ 套入上面的公式验证它等于输入的和。

代码

#include
#include
using namespace std;
void s() {
int n, tot\_lost;
cin >> n >> tot\_lost;
int maxx = -1;
int tot\_found = 0;
for (int i = 0; i < n; i++) {
int x;
cin >> x;
tot\_found += x;
maxx = max(maxx, x);
}
int tot = tot\_found + tot\_lost;
int f = sqrt(double(tot \* 2));
int e = f \* (f + 1) / 2;
if (e == tot && f >= maxx) cout << "YES" << endl;
else cout << "NO" << endl;
}
int main() {
ios::sync\_with\_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
while (n--) {
s();
}
return 0;
}

链接:CF1744A

解释

这道题目就是说每个测试都有一个数组和一个同样长的字符串,每次可以把数组中的一个数批量换成一个小写英文字母,问给定的组合是否合法?

思路

建一个数组$x$,相当于一个表,在 $x\_i$ 上记录 $i$。表示的字母,只要以后读到 $x\_i$ 上的值不等于当前的就直接 NO

代码

时间 15ms

// LUOGU\_RID: 91222907
#include
using namespace std;
void s() {
int n;
cin >> n;
int z[n];
for (int i = 0; i < n; i++) cin >> z[i];
string st;
cin >> st;
int map[55];
for (int i = 0; i < 55; i++) map[i] = -1;
for (int i = 0; i < n; i++) {
if (map[z[i]] == -1) {
map[z[i]] = st[i] - 'a';
} else {
if (map[z[i]] != st[i] - 'a') {
cout << "NO" << endl;
return;
}
}
}
cout << "YES" << endl;
}
int main() {
ios::sync\_with\_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
while (n--) {
s();
}
return 0;
}

我的博客即将同步至腾讯云开发者社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=32jh0ae4lqsko


一道DP(动态规划)、01背包的模板题(么?)。

洛谷链接P1048

DP是什么?

DP是一种“用空间换时间”的算法,它将已经算好的答案存下来(子问题),再从父问题获取子问题的答案。

此题解释

给出采每种药花费的时间和价值,问在给定的时间内最多采药多少钱?

怎么写?

对于每种药,遍历那个f,假如装不进去或者装进去费空间的要死那就抄上一个;假如可以的话那就装进去!

代码!

#include
using namespace std;
int main() {
ios::sync\_with\_stdio(false);
cin.tie(nullptr);
int t, n;
cin >> t >> n;
int v[n + 1]; //价值
int w[n + 1]; //时间
int f[101][1001];
for (int i = 1; i <= n; i++) cin >> w[i] >> v[i];
for (int i = 1; i <= n; i++)
for (int j = t; j >= 0; j--) {
if (j < w[i])
f[i][j] = f[i - 1][j]; //抄上一个(装不进去)
else if ( f[i - 1][j] > f[i - 1][j - w[i]] + v[i] )
f[i][j] = f[i - 1][j]; //抄上一个(装进去费空间)
else
f[i][j] = f[i - 1][j - w[i]] + v[i]; //装进去!
}
cout << f[n][t];
return 0;
}