左偏树
堆
满足父节点权值总是大于/小于两个子节点的特殊的二叉树.
左偏树
一种能在 \(\mathrm{O}(\log n)\) 时间内合并的堆,优势是稳定且码量小于 Fibonacci堆。
将结点封装为结构体 :
struct Node {
int val,ch[2],dist;
}T[N];
左偏树的 dist
定义一个结点的 dist
如下 :
定义没有左儿子或右儿子的结点为 外结点 ,其dist
为 1,空结点 dist
为 0,不是外节点的节点 dist
为其到子树中最近的外节点的距离加一。
为了维护左偏的性质,左偏树一个结点的左儿子的 dist
都要大于右儿子的 dist
。
模板
维护一个小根堆,删除堆顶就是把两个儿子合并.
维护元素在哪个堆里就用并查集即可.
int dsu[N];
int find(int x) {
return dsu[x] == x ? x : dsu[x] = find(dsu[x]);
}
struct Node {
int val,ch[2],dist;
}T[N];
int merge(int x,int y) {
if(!x || !y) return x | y;
if(T[x].val > T[y].val) std::swap(x,y);
T[x].ch[1] = merge(T[x].ch[1],y);
if(T[T[x].ch[1]].dist > T[T[x].ch[0]].dist)
std::swap(T[x].ch[0],T[x].ch[1]);
T[x].dist = T[T[x].ch[1]].dist + 1;
return x;
}
bool vis[N];
int main() {
init_IO();
int n = read(),m = read();
rep(i,1,n) {
T[i].val = read();
dsu[i] = i;
}
while(m--) {
int op = read(),x = read();
if(op & 1) {
int y = read();
int fx = find(x),fy = find(y);
if(fx == fy || vis[x] || vis[y])
continue;
dsu[fx] = dsu[fy] = merge(fx,fy);
}
else {
if(vis[x]) {
putC('-'),putC('1');
enter;
continue;
}
int fx = find(x);vis[fx] = 1;
write(T[fx].val),enter;
dsu[fx] =
dsu[T[fx].ch[0]] =
dsu[T[fx].ch[1]] =
merge(T[fx].ch[0],T[fx].ch[1]);
}
}
end_IO();
return 0;
}
罗马游戏
没啥区别的模板题.
Monkey king
增加了把堆顶减半的操作,显然直接减半就不符合堆本身的性质了,考虑先把这个元素取出,减半然后合并回去,在合并之前清空结点信息.
断罪者
有趣的题目
城池攻占
对于树形结构,每个结点均维护左偏树的一个根.
船新技巧之左偏树整体打表记.
就像线段树一样,从祖先向下传即可.
这一题输入保证 \(f_i < i\) , 于是直接从 \(f_i\) 更新深度即可,甚至不需要DFS.
并且根据这条性质,直接倒序遍历 \(n\) 个结点就能达到从叶子向根走这一过程.
作者:AstatineAi
本文为 作者 AstatineAi 的原创,遵循CC BY-NC-SA 4.0 版权协议,转载请附上原文出处链接及本声明。