HDU 1512 Monkey King(左偏树+并查集)

题目链接:Click here

题意:有n个猴子,一开始每个猴子只认识自己。每个猴子有一个能力值,能力值越大表示这个猴子越厉害。如果2个猴子不认识,他们就会找他们认识的猴子中能力最强的出来决斗,决斗的2个猴子力量值减半,然后这2拨猴子就都认识了,不打不相识嘛。现在给m组询问,如果2只猴子相互认识,输出-1,否则他们各自找自己认识的最厉害的猴子单挑,求决斗完后这拨猴子力量最大值。

思路:两拨不认识得猴子决斗后认识,涉及到合并,于是想到并查集。又要求两拨猴子的最强战斗力,大顶堆比较合适,而堆得合并,用到了刚看到的左偏树。

    左偏树:(引用百度百科)
    [性质1] 节点的键值小于或等于它的左右子节点的键值。
    [性质2] 节点的左子节点的距离不小于右子节点的距离。
    [性质3] 节点的距离等于它的右子节点的距离加1。
    我们的印象中,平衡树是具有非常小的深度的,这也意味着到达任何一个节点所经过的边数很少。左偏树并不是为了快速访问所有的节点而设计的,它的目的是快速访问最小节点以及在对树修改后快速的恢复堆性质。从图中我们可以看到它并不平衡,由于性质2的缘故,它的结构偏向左侧,不过距离的概念和树的深度并不同,左偏树并不意味着左子树的节点数或是深度一定大于右子树。

代码:

#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
#include <cstdlib>

using namespace std;

int father[100005];
struct Monkey
{
    int left, right;
    int dis;
    int blood;
} LTree[100005];

int findx(int x)
{
    if(x == father[x])return x;
    return father[x] = findx(father[x]);
}

int merges(int x,int y)
{
    int left, right;
    if( x == 0 )
        return y;
    if( y == 0)
        return x;
    if( LTree[x].blood < LTree[y].blood) //大顶堆
        swap(x,y);
    LTree[x].right = merges(LTree[x].right,y);
    left = LTree[x].left;
    right = LTree[x].right;
    father[right] = x;   //修改并查集的根
    if(LTree[left].dis < LTree[right].dis)
        swap(LTree[x].left,LTree[x].right);
    if(LTree[x].right == 0)
        LTree[x].dis = 0;
    else
        LTree[x].dis = LTree[LTree[x].right].dis + 1;
    return x;
}
int del(int x)
{//删除根节点,并合并左右子树
    int left, right;
    left = LTree[x].left;
    right = LTree[x].right;
    father[left] = left;
    father[right] = right;
    LTree[x].left = LTree[x].right = LTree[x].dis = 0;
    return merges(left, right);
}
void fight(int a,int b)
{//两棵树根节点血量减半,并与子树再次合并
    int left,right;
    LTree[a].blood /= 2;
    LTree[b].blood /= 2;
    left = del(a);
    right = del(b);
    left = merges(left, a);
    right = merges(right, b);
    left = merges(left,right);
    printf("%d\n",LTree[left].blood);
}
int main()
{
    int n, m, x, y, a, b;
    while(scanf("%d",&n) != EOF)
    {
        for(int i = 1; i <= n; i++)
        {
            scanf("%d",&LTree[i].blood);
            LTree[i].left = 0;
            LTree[i].right = 0;
            LTree[i].dis = 0;
            father[i] = i;
        }
        scanf("%d",&m);
        for(int i = 1; i <= m; i++)
        {
            scanf("%d%d",&x,&y);
            a = findx(x);
            b = findx(y);
            if(a == b)
                printf("-1\n");
            else fight(a, b);
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值