您的位置:首页 > 其它

ZOJ 2334 Monkey King

2013-05-16 11:25 381 查看
学了左偏树.

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

以下内容转自: http://www.cppblog.com/guogangj/archive/2009/10/30/99833.html
树这个数据结构内容真的很多,上一节所讲的二叉堆,其实就是一颗二叉树,这次讲的左偏树(又叫“左翼堆”),也是树。

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

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

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



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

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







合并操作的代码如下:

?
#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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: