ZOJ 2334 Monkey King

13 篇文章 0 订阅
6 篇文章 0 订阅

学了左偏树.

这题用左偏树来合并集合,并查集判是否在同一集合,无敌组合.

以下内容转自: http://www.cppblog.com/guogangj/archive/2009/10/30/99833.html

树这个数据结构内容真的很多,上一节所讲的二叉堆,其实就是一颗二叉树,这次讲的左偏树(又叫“左翼堆”),也是树。

二叉堆是个很不错的数据结构,因为它非常便于理解,而且仅仅用了一个数组,不会造成额外空间的浪费,但它有个缺点,那就是很难合并两个二叉堆,对于“合并”,“拆分”这种操作,我觉得最方面的还是依靠指针,改变一下指针的值就可以实现,要是涉及到元素的移动,那就复杂一些了。

左偏树跟二叉堆比起来,就是一棵真正意义上的树了,具有左右指针,所以空间开销上稍微大一点,但却带来了便于合并的便利。BTW:写了很多很多的程序之后,我发觉“空间换时间”始终是个应该考虑的编程方法。:)

左偏左偏,给人感觉就是左子树的比重比较大了,事实上也差不多,可以这么理解:左边分量重,那一直往右,就一定能最快地找到可以插入元素的节点了。所以可以这样下个定义:左偏树就是对其任意子树而言,往右到插入点的距离(下面简称为“距离”)始终小于等于往左到插入点的距离,当然了,和二叉堆一样,父节点的值要小于左右子节点的值。

如果节点本身不满,可插入,那距离就为0,再把空节点的距离记为-1,这样我们就得出:父节点的距离 = 右子节点距离 + 1,因为右子节点的距离始终是小于等于左子节点距离的。我把距离的值用蓝色字体标在上图中了。

左偏树并一定平衡,甚至它可以很不平衡,因为它其实也不需要平衡,它只需要像二叉堆那样的功能,再加上合并方便,现在来看左偏树的合并算法,如图:


合并操作的代码如下:
?
Function Merge(A, B)
     If A = NULL Then return  B
     If B = NULL Then return  A
     If key(B) < key(A) Then swap(A, B)
     right(A) ← Merge(right(A), B)
     If dist(right(A)) > dist(left(A)) Then
         swap(left(A), right(A))
     If right(A) = NULL Then dist(A) ← 0
     Else dist(A) ← dist(right(A)) + 1
     return  A
End Function
#include <iostream>
#include <cstdio>
using namespace std;
const int maxn = 100010;
struct node{
	int l, r, id, dis, v;
}nds[maxn];

int n, m;

int merge(int a, int b){
	if(a == b) return a;
	if(a == 0)return b;
	if(b == 0)return a;
	if(nds[a].v < nds[b].v)swap(a, b);
	nds[a].r = merge(nds[a].r, b);
	nds[nds[a].r].id = a;
	if(nds[nds[a].l].dis < nds[nds[a].r].dis) swap(nds[a].l,nds[a].r); //右子树的距离如果大于左边则交换左右两树, 使其满足左偏性质
	if(nds[a].r == 0)nds[a].dis = 0;
	else nds[a].dis = nds[nds[a].r].dis + 1; 
	return a;
}
int find(int p){
	return nds[p].id == p ? p : nds[nds[p].id].id = find(nds[p].id);
}
void readInt(int & t){
	char ch;
	t = 0;
	ch = getchar();
	while(!(ch >= '0' && ch <= '9'))ch = getchar();
	while (ch >= '0' && ch <= '9'){
		t = t * 10 + ch - '0';
		ch = getchar();
	}
}
int delNode(int a){
	int l = nds[a].l;
	int r = nds[a].r;
	nds[l].id = l;
	nds[r].id = r;
	nds[a].l = nds[a].r = nds[a].dis = 0;
	nds[a].v /= 2;
	return merge(l, r);
}
void init(){
	for (int i = 1; i <= n; ++i){
		nds[i].id = i;
		nds[i].dis = 0;
		nds[i].l = 0;
		nds[i].r = 0;
		readInt(nds[i].v);
	}
}
int main(){
	while (scanf("%d", &n) == 1){
		init();
		scanf("%d", &m);
		for (int i = 0; i < m; ++i){
			int u, v;
			readInt(u), readInt(v);
			int pu = find(u), pv = find(v);
			if(pu == pv){//同一集合
				printf("-1\n");
			}else{
				int tur = delNode(pu);//这个结点
				tur = merge(tur, pu);//重新加入树中

				int tvr = delNode(pv);
				tvr = merge(tvr, pv);
				int ans = nds[merge(tur, tvr)].v;//合并两颗树
				printf("%d\n",ans);
			}
		}
	}
	return 0;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值