【算法】基础数据结构的使用

单调栈

通过维护一个单调递增或者单调递减的栈, 来求解元素左右大小边界的问题.

尝试思考下面这个问题, 对于第 1 个元素, 他往右边看, 除了比他高的, 都会被挡住(如第 3 个和第 4 个元素), 而这个就是单调栈的核心思想, 在这里被挡住的元素对当前节点来说是没用的信息, 所以单调栈维护的是当前节点往右边看, 不会被挡住的元素.

来看一道例题

这道例题中, 需要我们找到第一个大于当前元素的元素的下标, 那就是一个大小边界的问题, 我们直接从这个序列的最右边开始遍历, 然后为每一个节点维护一个单调栈. 即当前栈中如果存在比他小的, 就直接 pop 出去即可, 因为对于当前元素后面的元素, 这些都是被挡住的元素.

代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include<iostream>
#include<stack>
using namespace std;

const int MAXN = 3e6+2;
int n, num[MAXN], ans[MAXN];
stack<int> sta;

int main(){
cin>>n;
for(int i=1; i<=n; i++){
cin>>num[i];
}
for(int i=n; i>=1; i--){
// 如果发现当前栈内有元素比自己小
while(!sta.empty() && num[i]>num[sta.top()]){
sta.pop();
}
ans[i] = sta.empty() ? 0:sta.top();
sta.push(i);
}
for(int i=1; i<=n; i++){
cout<<ans[i]<<" ";
}
}

优先级队列

优先级队列(priority queue)是由二叉堆实现的, pushpop 的复杂度是 O(logn) ,当用优先队列去处理 pair 元素时, 他会按照 pair 中大的那个进行优先级排序, 优先级队列也是队列, 队列中如果 pop 了一个元素, 他不会释放分配过的内存, 只有析构的时候才会一起分配, 所以如果你 pop 一个元素后马上使用 back 去读取他还是能读到之前的元素的.

例题:

优先队列模板题, 维护一个大端序的优先队列, 每次遇到 IN 事件就加入到队列中, 遇到 OUT 事件就从队列中取出, 如果队列为空就输出 EMPTY, 需要注意的是这里如果优先权一样, 就要选择最早来排队的病人, 所以要重构 operator, 然后把这个 operator 应用到优先队列中.

代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include<iostream>
#include<queue>
using namespace std;

//存储病人信息的结构体
struct node{
int level, pos;
friend bool operator < (const int x, const int y){
if(x.level != y.level)
return x.level < y.level;
else
return x.pos > y.pos;
}
}

int main(){
int n,a,b;
string opt;
int cnt=0;
while(cin>>n){
priority_queue<node> que[4];
while(n--){
cin>>opt;
if(opt == 'IN'){
cin>>a>>b;
cnt++;
que[a].push(b)
}
if(opt == 'OUT'){
cin>>a;
if(que[a].empty())
cout<<"EMPTY"<<endl;
else{
cout<<que[a].top.pos<<endl;
que[a].pop();
}
}
}
}
}

哈希表

哈希表就是通过映射将字符串通过一些加密函数转换后进行存储, 可以进行状态压缩, 将大集合映射成小集合.

一般的哈希函数很容易发生值冲突, 这里给一个字符串哈希的模板代码:

根据公式:

$$ Hash(S) = (\sum_{i=1}^nc_i * base^n-1) \mod p $$

1
2
3
4
5
6
7
8
9
int getHash(string s){
int res=0;
int base=31;
int mod = 1222827239;
for(int i=0;i<s.size();i++){
res = (res*base+(s[i]-'a')) % mod;
}
return res;
}

哈希状态压缩例题:

需要注意的是, 这道题的范围 $N \leq 10000, M_{max} \leq 1500$, 如果使用 vector 存储数据, 需要花费的最大空间计算如下:

$$ \frac{10000 \times 1500}{1 \times 1024 \times 1024} \approx 15 \text{ MB} $$

这里的 $1$ 是因为对于每个字符是 char 格式, 占一个字节. 最大需要消耗 15mb 的内存空间, 所以会爆掉, 这里要用哈希去状态压缩, 然后使用 set 去重, 最后返回 size 就是答案, 代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include<iostream>
#include<string>
using namespace std;

int getHash(string s){
int res=0;
int base=31;
int mod = 1222827239;
for(int i=0;i<s.size();i++){
res = (res*base+(s[i]-'a')) % mod;
}
return res;
}

int main(){
int n;
cin>>n;
set<int> res;
for(int i=0; i<n;i++){
string str;
cin>>str;
res.insert(getHash(str));
}
cout<<res.size()<<endl;
return 0;
}

双重哈希状态压缩, 例题:

这里内存和时间都卡的很死, 思路是使用双重哈希, 具体做法是用一个 vector 数组顺序保存词典索引和词典内容, 然后用 map 去记录对应 hash 的下标. 因为 map 里面是红黑树, 如果直接存映射会爆内存的.

代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include<iostream>
#include<map>
#include<string>
#include<utility>
using namespace std;

int getHash(string s){
// 注意这里是 longlong 防止溢出
long long res=0;
int base=65337;
int mod = 1e9+7;
for(int i=0;i<s.size();i++){
res = (res*base+(s[i]-'a')) % mod;
}
return res;
}

int main(){
map<int, int>hash2pos;
vector<string> dicts;
string str;
while(getline(cin,str)&& str!="@END@"){
int pos = str.find("]");
string dict = str.substr(0,pos+1);
string fun = str.substr(pos+2,str.length()-1);
int dict_hash = getHash(dict);
int fun_hash = getHash(fun);
// 将 dict 和 function 存入 vector 数组
dicts.push_back(dict);
dicts.push_back(fun);
// 用 map 存 vector 的数组索引
hash2pos[dict_hash] = dicts.size()-1;
hash2pos[fun_hash] = dicts.size()-2;
}
int n;
cin>>n;
while(n--){
cin>>str;
int hashvalue = getHash(str);
if(str[0]='['){
if(hash2pos.count[hashvalue]>0){
cout<<dict[hash2pos[hashvalue]]<<endl;
}
else{
cout<<"what?"<<endl;
}
}
else{
if(hash2pos.count[hashvalue]>0){
string res = dicts[hash2pos[hashvalue]];
// 去掉方括号
res = res.substr(1,res.size()-2);
cout<<res<<endl;
}
else{
cout<<"what?"<<endl;
}
}
}
return 0;
}