El pueblo unido jamas serà vencido!

左偏树

满足父节点权值总是大于/小于两个子节点的特殊的二叉树.

左偏树

一种能在 \(\mathrm{O}(\log n)\) 时间内合并的堆,优势是稳定且码量小于 Fibonacci堆。

模板题

将结点封装为结构体 :

struct Node {
    int val,ch[2],dist;
}T[N];

左偏树的 dist

定义一个结点的 dist 如下 :

定义没有左儿子或右儿子的结点为 外结点 ,其dist 为 1,空结点 dist 为 0,不是外节点的节点 dist 为其到子树中最近的外节点的距离加一。

为了维护左偏的性质,左偏树一个结点的左儿子的 dist 都要大于右儿子的 dist

模板

P3377 【模板】左偏树(可并堆)

维护一个小根堆,删除堆顶就是把两个儿子合并.

维护元素在哪个堆里就用并查集即可.

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;
}

记录

罗马游戏

P2713 罗马游戏

没啥区别的模板题.

记录

Monkey king

P1456 Monkey King

增加了把堆顶减半的操作,显然直接减半就不符合堆本身的性质了,考虑先把这个元素取出,减半然后合并回去,在合并之前清空结点信息.

记录

断罪者

有趣的题目

在另一篇博客里

记录

城池攻占

对于树形结构,每个结点均维护左偏树的一个根.

船新技巧之左偏树整体打表记.

就像线段树一样,从祖先向下传即可.

这一题输入保证 \(f_i < i\) , 于是直接从 \(f_i\) 更新深度即可,甚至不需要DFS.

并且根据这条性质,直接倒序遍历 \(n\) 个结点就能达到从叶子向根走这一过程.

posted @ 2022-01-26 21:38  AstatineAi  阅读(26)  评论(0编辑  收藏  举报