寒假day2 2.3 ds
讲师:杨宁远,NOI2022Au,rk20,from 成都七中
DS
-
STL
-
LCA(倍增、欧拉序、四毛子优化欧拉序)
-
笛卡尔树(四毛子求RMQ)
-
二叉堆
-
启发式合并
-
左偏树
-
树状数组
-
线段树
-
哈希
-
字典树
-
Splay
-
Treap
-
替罪羊树
list
auto 定义指针。
*i 访问元素。
prev(i) next(i) 访问前驱、后继的值。
rbrgen rend 含义相反。
front back 放回头元素和尾元素。
insert(iterator,value),会在迭代器前插入元素。
erase(iterator),删除元素。
a.swap(b):O(1)
merge 把两个有序的 list 合并。a.merge(b)
vector
a.resize(n),强制把 a 的大小变成 \(n\)。往后加 \(0\)。
原理:倍增数组
删除元素空间不会变小。
shrink_to_fit 强制把空间变为 fit ,O(元素个数)
支持随机类型访问
insert erase O(后面元素个数)
a.swap(b) O(1)
set
要求给定元素可以比较
结构体内自定义排序
bool operator <(const node &x)const{
}
erase(值或迭代器)
find(值) 返回迭代器 不存在——set.end()
count(值) 返回0/1
lower_bound upper_bound 返回迭代器
复杂度 \(O(\log)\)。
multiset
count(值) 复杂度 O(log+元素个数)。
unordered_set
C++11 哈希实现
无序 复杂度 O(1)
map/unordered_map
访问空下标会创建元素 应该用 mp.find(x)==mp.end()) 进行判断
lower_bound upper_bound 返回迭代器
queue
空间常数大。
a.swap(b) O(1)
deque
时空常数巨大
底层实现用两个 vector 拼接起来
存在 shrink_to_fit
支持下标访问
建议用 list 或自己实现 deque
priority_queue
本质是二叉堆,实现是 vector
加入删除复杂度 \(O(\log)\),常数小。类似 BIT。
ST表与RMQ
\(f_{i,k}=min(a_{i\sim i+2^k-1})\)
递推:\(f_{i,k}=min(f_{i,k-1},f_{i+2^{k-1},k-1)\)
询问:\(min(f_{l,k},f_{r-2^k+1,k-1})\)
可以用于区间线性基。
__lg(x) 返回 \(\lfloor\log x\rfloor\)。
倍增求LCA
\(p_{u,k}\) 表示 \(u\) 往上跳 \(2^k\) 步到哪。
\(p_{u,k}=p_{p_{u,k-1},k-1}\)
把较深的跳到同一高度。
注意判断跳完以后是不是一个点。
只要不会相遇就一直跳。
最后多跳一步。
RMQ求LCA
记录 dfs 序(可重)。
记录每个节点的 dfn
两个节点的 LCA 一定在欧拉序中两点 dfn 中间一段区间,是深度最小的。
用 ST 表解决(深度最小 \(\rightarrow\) RMQ)。
预处理 \(O(n\log n)\) 询问 \(O(1)\)
四毛子求LCA
+1-1RMQ
分块,分 \(\log n/2\) 块。
可以发现,欧拉序相邻的深度差为 \(1\),利用四毛子。
\(O(n)\) 预处理,\(O(1)\) 查询。
笛卡尔树
不断找最小值,把区间分裂,划分左右子树。
增量法规建笛卡尔树。
假设求出了 $1\sim i $ 的笛卡尔树,可以发现,\(i\) 号点一定在最靠右的链上,且一定没有右儿子,根据定义,这是显然的。
不可能是某个时刻走左儿子得到的。
建树
如果 \(i+1\) 号比 \(i\) 号大,右儿子。
如果小,往上跳,直到跳到某个点比 \(i+1\) 号大。
对于这个点,令 \(i+1\) 号为它的右儿子,原来的儿子链为 \(i+1\) 号的左儿子。
均摊线性。
性质:lca为区间最值。
构建的本质:维护右链。
四毛子求RMQ?
upd:
二叉堆
insert:向上更新。
erase:将根节点与数组最后一个叶子节点交换后向下调整。
应用:排序 动态维护最值
中位数
用两个堆 small large
不断维护大小平衡
始终尽量维护 small.size()<=large.size()<=small.size()+1
序列合并
二分做法:二分前 \(N\) 小的数中最大的一个,发现对于 \(a_i\) ,合法的 \(j\) 单调不降,双指针做。
堆:对每一个 \(a_i\) 加上 \(b_1\) 后放入堆里,对于最优的求出后继状态(把 \(b_j\) 向右扩展),放入堆里。
左偏树
特殊的二叉堆。
dist:从一点走右链能走多少步
左儿子的 dist 大于右儿子的 dist 。
右链是 \(\log\) 级别。
类似完全二叉树向左偏,而完全二叉树的右链是 \(\log\) 的。
启发式合并
将 \(n\) 个堆合并,复杂度是 \(O(n\log^2 n)\)。
左偏树的合并是严格 \(\log\) 的。
int merge(int x,int y){
if(!x | !y) return x | y;
if(val(x) > val(y)) swap(x, y);
r(x) = merge(r(x), y);
if(dst(l(x)) < dst(r(x))) swap(l(x), r(x));//保证满足左偏树的性质
dst(x) = dst(r(x)) + 1;
return x;
}
不会左偏树。
从归并排序的角度考虑合并
罗马游戏
对每个人用并查集维护在哪个团,用左偏树维护团。
删除堆顶元素:合并左子树、右子树,标记死亡。
Monkey King
通过此题观察左偏树类题的特征:往往类似于并查集,但一般会查询并查集内最值,并对最值有所更改,这是并查集无法维护的,所以会采用左偏树维护。
武力值减小可以视作把原武力值删去,加入一个原武力值一半的节点。
树状数组
\(i\) 号节点维护 \(i-2^{lowbit(i)}+1\sim i\) 的总和。
令所有 \(i\oplus 2^t\) 成为 \(i\) 的儿子(\(t<lowbit(i)\))。
区间加、区间求和
问题:区间价,查询前缀和
令 \(b_i=a_i-a_{i-1}\),当区间加时,发现 \(b_l+=v,b_{r+1}-=v\)。
可以发现,\(b_1+b_2=a_1-a_0+a_2-a_1=a_2\)。
所以,\(a_i=\sum\limits_{j=1}^ib_j\)。
考虑查询前缀和。
发现:
\(s_i=\sum\limits_{j=1}^ia_j=\sum\limits_{j=1}^i\sum\limits_{k=1}^jb_k\)。
发现 \(b_k\) 产生了 \(i-k+1\) 次贡献。
所以 \(s_i=\sum\limits_{j=1}^ib_j\times (i-j+1)=\sum\limits_1^ib_j\times (i+1)-\sum\limits_1^ib_j\times j\)。
维护 \(b_j\times j\)
二维树状数组
loj133
单点加,矩形查。
没听。
也可以矩形加,矩形查。
线段树
必须满足标记具有分配律。
方差
维护区间和 & 区间平方和。
维护区间平方和时,采取完全平方公式展开后提取公因式发现一个比较容易维护的式子。
哈希
追求 \(O(1)\) 修改,\(O(1)\) 查询。
取一个不规则的函数,使得 \(f(data)\rightarrow 0\le int\le 10^6\)。
取模是一个常见的哈希函数。
对于字符串,取 base ,使字符串变成 base 进制下的数:\(f(s)=\sum\limits_0^n(s_i-'a'+1)\times base^i\)。
太大可以取模。
如果数据个数过多,会发生哈希冲突。
对于 \(a\ne b\),存在 \(f(a)=f(b)\)。
\(\sqrt{n}\) 落在 \(n\) 的范围内,存在大概 \(\frac{1}{2}\) 的概率使得存在冲突。
ln 的求和可以泰勒展开(求导)或微积分。
面对冲突,应人为修改 \(f()\)。
字符串哈希
比较 \(l\sim r\) 和 \(l_1\sim r_1\) 是否相同。
双哈希:取 \(p_1\) 和 \(p_2\) ,记录 pair ,fi 表示对 \(p_1\) 取模的结果,se 表示对 \(p_2\) 取模的结果。
自然溢出:易卡。
字符串哈希需要选取两个质数进行双哈希。
cc_hash_table gp_hash_table
unordered_map 较慢。
loj DNA序列
如果 \(k=100\) 需要双哈希,声称两个子串相等当且仅当 \(%p_1\) 相等且 \(%p_2\) 相等。
区间 \(\times -1\) ,求区间最大子段和
竟然上课口胡出来了
维护区间最大和、左起最大和、右起最大和、区间最小和、左起最小和、右起最小和,考虑合并。
面对 \(-1\),考虑最大和变成最小和,最小和变成最大和。
字典树
每条边挂一个字符。
构建考虑对于一个字符串,从上到下来走,如果对于一个字符走不到,就新建。
建议参考 OI-Wiki 。
Secret Message G
考虑对于信息建字典树,并且记录有多少信息在节点截止以及有多少信息经过节点。对于暗号,从树根向下走,统计经过多少完整信息(路径上节点权值之和),当截止后,统计有多少信息经过那个点,两者相加即为答案。
平衡树
!!!
平衡二叉查找树,尽可能把二叉树两棵子树大小平衡一下。
Splay Treap 替罪羊树 红黑树 AVL(没用)
最常用:Treap
Splay 只有在 LCT 中用到
替罪羊树只有在 K-DT中用到
Splay
通过不断将节点旋转,使得仍满足 BST。
不能持久化。
\(O(\log n)\)
旋转本质:某个节点上移一个位置。
保证:
-
中序遍历不变
-
信息正确有效
-
root指向旋转后的根
左旋:左边变成右边
右旋相反
maintain 类似 push_up
Splay 操作:不断对一个节点 rotate
Treap
treap=tree+heap
treap 每个节点额外存储一个关键值,根据关键值调整结构。
如果随机生成关键值,树高期望 \(O(\log)\)。
合并 \(O(\log)\)。
fhq-Treap
核心:分裂、合并
merge:合并两个 treap
先决条件:\(i\in x,j\in y,i<j\)
split:一个 treap 分裂成两个
自己看吧
替罪羊树
用于 KDT
文艺平衡树
以每个数下标作为关键字
先 split(l-1)->split(r)->打tag 子树翻转
kmp
对于 \(1\sim i\),求出 \(nxt_i\),即最大的 \(k\) 满足 \(k\le i,1\sim k=i-k+1\sim i\)。
\(nxt_{i+1}\leq nxt_i+1\)
动物园
- 没听