基础数据结构
链表与邻接表
数组模拟单链表(邻接表)
下标从零开始
第k个插入点下标为k-1
int head,v[N],ne[N],i;//头节点下标,该节点值,节点的next指针,当前点下标
void init(){
head=-1;//指空
i=0;
}
void insert(int a){
v[i]=a,ne[i]=head,head=idx++;
}//头插
void insert_k(int k,int x){
v[i]=x,ne[i]=ne[k],ne[k]=i++;
}
void remove(){
head =ne[head];
}//删头结点的时候记得特判一下
void remove_k(int k){
ne[k]=ne[ne[k]];
}
数组模拟双链表(优化用)
int v[N],l[N],r[N],i;
void init(){
l[0]=0,r[0]=1;
i=2;
}
//右插
void insert(int k,int x){
v[i]=x;
l[i]=k,r[i]=r[k];
l[r[k]]=i,r[k]=i++;
}//左插直接把k变为l[k]
void remove(int k){
l[r[k]]=l[k];
r[l[k]]=r[k];
}//左变右,右变左
栈与队列
数组模拟(快
单调xx:过滤没有用的数据
单调栈
找临近比它大/小的数
stack<int>q;
for (int i = 1; i <= n; i++) {
//cin >> a[i];
while (!q.empty() && q.top() <= a[i])
q.pop();
ans += q.size();
q.push(a[i]);
}//满足某种性质才入栈
//POJ - 3250 http://poj.org/problem?id=3250
单调队列
滑动窗口最值
deque<int>q;
for(int i=1;i<=n;i++){
while(!q.empty()&&i-k>=q.front())
q.pop_front();
while(!q.empty()&&a[q.back()]>=a[i])//min
q.pop_back();
q.push_back(i);
if(i>=k)
cout<<a[q.front()]<<' ';
}//max就改个符号,中间记得重置deque
应用:多重背包优化
kmp
资料:https://oi-wiki.org/string/kmp/
Next[i], 匹配
- 计算前缀函数
含义:最长的相等的真前缀与真后缀的长度
vector<int> prefix_function(string s) {
int n = (int)s.length();//总长度
vector<int> pi(n);
for (int i = 1; i < n; i++) {
int j = pi[i - 1];
while (j > 0 && s[i] != s[j]) j = pi[j - 1];//不满足就接着往回找
if (s[i] == s[j]) j++;//后推
pi[i] = j;//长度
}
return pi;
}
- KMP
// s是长文本,p是模式串
//求模式串的Next数组:
void Next_pre(string p,vector<int>&Next){
for (int i = 1, j = 0; i < (int)p.size(); i ++ ){
//i是当前遍历到的后缀,j是前缀
while (j && p[i] != p[j])
j = Next[j-1];//前后缀不相等就一直跳转
if (p[i] == p[j])
j ++ ;//前后缀相等就往后
Next[i] = j;
}
}
// 匹配
int KMP_match(string s,string p,int begin){
vector<int>Next(p.length());
Next_pre(p,Next);
for(int i=begin,j=0;i<(int)s.length();i++){
while (j && s[i] != p[j])
j = Next[j-1];//匹配不成功,一直回跳
if (s[i] == p[j])
j ++ ;//匹配成功
if (j == p.length())
return i-p.length()+1;//完全匹配成功
}
}
Trie树
高效存储、查找字符串集合
从root,出发,按树的方式存;结尾打标记
int son[N][26],cnt[N],i;//下标0的点,是根节点,也是空节点;存节点的子节点;cnt[]存储以每个节点结尾的单词数量
void insert(char str[]){
int p=0;
for(int i=0;str[i];i++){
int u=str[i]-'a';
if(!son[p][u])
son[p][u]==++i;
p=son[p][u];
}
cnt[p]++:
}
int query(char str[]){
int p=0;
for(int i=0;str[i];i++){
int u=str[i]-'a';
if(!son[p][u])
return 0;
p=son[p][u];
}
return cnt[p];
}
并查集
拓展应用:https://oi-wiki.org/topic/dsu-app/
1. 朴素并查集
初始化
void makeSet(int size) {
for (int i = 0; i < size; i++) fa[i] = i;
}
路径压缩(查找)
int find(int x) {
if (x != fa[x])
fa[x] = find(fa[x]);
return fa[x];
}
合并
void Union(int x, int y) {
fa[find(x)] = find(y);
}
启发式合并
https://oi-wiki.org/ds/dsu-complexity/
2. 维护size
加一个数组size[N](仅祖宗节点有意义)
初始化
void makeSet(int size) {
for(int i=1;i<=n;i++){
fa[i]=i;
size[i]=1;
}
}
查找
不变
合并
void Union(int x, int y) {
if(find(x)==find(y))
return;
size[find(y)]+=size[find(x)];
fa[find(x)] = find(y);
}
3. 维护到祖宗节点距离
初始化
void makeSet(int size) {
for(int i=1;i<=n;i++){
fa[i]=i;
size[i]=0;//自己到自己
}
}
查找
int find(int x){
if(fa[x]!=x){
int u=find(fa[x]);
d[x]+=d[fa[x]];
fa[x]=u;
}
return fa[x];
}
合并
void Union(int x, int y) {
fa[find(x)] = find(y);
d[find(x)]=distance;
}
堆
小根堆(根节点最小)
#include<queue>
//默认大根堆
top() 访问堆顶元素(此时优先队列不能为空)
empty() 询问容器是否为空
size() 查询容器中的元素数量
push(x) 插入元素,并对底层容器排序
pop() 删除堆顶元素(此时优先队列不能为空)
//小根堆
priority_queue<int, deque<int>, greater<int> > q3;
堆拓展:https://oi-wiki.org/ds/heap/
Hash表
https://oi-wiki.org/ds/hash/
x mod 质数(超出数据范围的最小质数)
存储结构(解决冲突)
- 开放寻址法
int h[N];//开一个两倍的数组
int find(int x){
int t=(x%N+N)%N;
while(h[t]!=null&&h[t]!=x){//null表示一个不可能的数
t++;
if(t==N)
t=0;//循环
}
return t;
}
- 拉链法(开散列法)
拉一条链存在同一坐标下面
int h[N], e[N], ne[N], idx;
// 向哈希表中插入一个数
void insert(int x)
{
int k = (x % N + N) % N;//保证负数模出来为正
e[idx] = x,ne[idx] = h[k],h[k] = idx ++ ;
}
// 在哈希表中查询某个数是否存在
bool find(int x)
{
int k = (x % N + N) % N;
for (int i = h[k]; i != -1; i = ne[i])
if (e[i] == x)
return true;
return false;
}
封装后
struct hash_map { // 哈希表模板
struct data {
long long u;
int v, nex;
}; // 前向星结构
data e[SZ << 1]; // SZ 是 const int 表示大小
int h[SZ], cnt;
int hash(long long u) { return u % SZ; }
int& operator[](long long u) {
int hu = hash(u); // 获取头指针
for (int i = h[hu]; i; i = e[i].nex)
if (e[i].u == u) return e[i].v;
return e[++cnt] = (data){u, -1, h[hu]}, h[hu] = cnt, e[cnt].v;
}
hash_map() {
cnt = 0;
memset(h, 0, sizeof(h));
}
};
字符串哈希
快速判断两个字符串是否相等(替KMP
妙用:https://www.acwing.com/problem/content/843/
不能映射成0
核心思想:将字符串看成P进制数,P的经验值是131或13331,取这两个值的冲突概率低
小技巧:取模的数用2^64,这样直接用unsigned long long存储,溢出的结果就是取模的结果
typedef unsigned long long ULL;
ULL h[N], p[N]; // h[k]存储字符串前k个字母的哈希值, p[k]存储 P^k mod 2^64
// 初始化
p[0] = 1;
for (int i = 1; i <= n; i ++ )
{
h[i] = h[i - 1] * P + str[i];
p[i] = p[i - 1] * P;
}
// 计算子串 str[l ~ r] 的哈希值
ULL get(int l, int r)
{
return h[r] - h[l - 1] * p[r - l + 1];
}
数组模拟STL
队列
- 普通队列
// hh 表示队头,tt表示队尾
int q[N], hh = 0, tt = -1;
// 向队尾插入一个数
q[ ++ tt] = x;
// 从队头弹出一个数
hh ++ ;
// 队头的值
q[hh];
// 判断队列是否为空
if (hh <= tt)
{
}
- 循环队列
// hh 表示队头,tt表示队尾的后一个位置
int q[N], hh = 0, tt = 0;
// 向队尾插入一个数
q[tt ++ ] = x;
if (tt == N) tt = 0;
// 从队头弹出一个数
hh ++ ;
if (hh == N) hh = 0;
// 队头的值
q[hh];
// 判断队列是否为空
if (hh != tt)
{
}
- 单调队列
常见模型:找出滑动窗口中的最大值/最小值
int hh = 0, tt = -1;
for (int i = 0; i < n; i ++ )
{
while (hh <= tt && check_out(q[hh])) hh ++ ; // 判断队头是否滑出窗口
while (hh <= tt && check(q[tt], i)) tt -- ;
q[ ++ tt] = i;
}
栈
- 栈
// tt表示栈顶
int stk[N], tt = 0;
// 向栈顶插入一个数
stk[ ++ tt] = x;
// 从栈顶弹出一个数
tt -- ;
// 栈顶的值
stk[tt];
// 判断栈是否为空
if (tt > 0)
{
}
- 单调栈
常见模型:找出每个数左边离它最近的比它大/小的数
int tt = 0;
for (int i = 1; i <= n; i ++ )
{
while (tt && check(stk[tt], i)) tt -- ;
stk[ ++ tt] = i;
}
堆
priority_queue
// h[N]存储堆中的值, h[1]是堆顶,x的左儿子是2x, 右儿子是2x + 1
// ph[k]存储第k个插入的点在堆中的位置
// hp[k]存储堆中下标是k的点是第几个插入的
int h[N], ph[N], hp[N], size;
// 交换两个点,及其映射关系
void heap_swap(int a, int b)
{
swap(ph[hp[a]],ph[hp[b]]);
swap(hp[a], hp[b]);
swap(h[a], h[b]);
}
void down(int u)//小的下沉
{
int t = u;
if (u * 2 <= size && h[u * 2] < h[t]) t = u * 2;//下沉到左儿子
if (u * 2 + 1 <= size && h[u * 2 + 1] < h[t]) t = u * 2 + 1;
if (u != t)//根节点不是最小值
{
heap_swap(u, t);
down(t);
}
}
void up(int u)//大的上浮
{
while (u / 2 && h[u] < h[u / 2])
{
heap_swap(u, u / 2);
u >>= 1;//UP
}
}
// O(n)建堆
for (int i = n / 2; i; i -- ) down(i);
STL补充
set, map, multiset, multimap,
基于平衡二叉树(红黑树),动态维护有序序列
++, -- 返回前驱和后继,时间复杂度 O(logn)
set/multiset
insert() 插入一个数
find() 查找一个数
count() 返回某一个数的个数
erase()
(1) 输入是一个数x,删除所有x O(k + logn) (2) 输入一个迭代器,删除这个迭代器
lower_bound()/upper_bound()
lower_bound(x) 返回大于等于x的最小的数的迭代器
upper_bound(x) 返回大于x的最小的数的迭代器
map/multimap
insert() 插入的数是一个pair
erase() 输入的参数是pair或者迭代器
find()
[] 注意multimap不支持此操作 时间复杂度是 O(logn)
lower_bound()/upper_bound()
unordered_set, unordered_map, unordered_multiset, unordered_multimap, 哈希表
和上面类似,增删改查的时间复杂度是 O(1)
不支持 lower_bound()/upper_bound(),迭代器的++,--
bitset, 压位
https://oi-wiki.org/lang/csl/bitset/
bitset<10000> s;
~, &, |, ^
>>, <<
==, !=
[]
count() 返回有多少个1
any() 判断是否至少有一个1
none() 判断是否全为0
set() 把所有位置成1
set(k, v) 将第k位变成v
reset() 把所有位变成0
flip() 等价于~
flip(k) 把第k位取反