Monkey King(猴王)——可并堆走起

猴王

时间限制: 1 Sec  内存限制: 128 MB

题目描述

    很久很久以前,在一个广阔的森林里,住着n只好斗的猴子。起初,它们各干各的,互相之间也不了解。但是这并不能避免猴子们之间的争吵,当然,这只存在于两个陌生猴子之间。当两只猴子争论时,它们都会请自己最强壮的朋友来代表自己进行决斗。显然,决斗之后,这两只猴子以及它们的朋友就互相了解了,这些猴子之间将再也不会发生争论了,即使它们曾经发生过冲突。
    假设每一只猴子都有一个强壮值,每次决斗后都会减少一半(比如10会变成5,5会变成2.5)。并且我们假设每只猴子都很了解自己。就是说,当它属于所有朋友中最强壮的一个时,它自己会站出来,走向决斗场。

输入

输入分为两部分。
第一部分,第一行有一个整数n(n<=100000),代表猴子总数。
接下来的n行,每行一个数表示每只猴子的强壮值(小于等于32768)。
第二部分,第一行有一个整数m(m<=100000),表示有m次冲突会发生。
接下来的m行,每行包含两个数x和y,代表第x个猴子和第y个猴子之间发生冲突。

输出

输出每次决斗后在它们所有朋友中的最大强壮值。数据保证所有猴子决斗前彼此不认识。

样例输入

5
20
16
10
10
4
4
2 3
3 4
3 5
1 5

样例输出

8
5
5
10

乍一看这道题,真的厉害。不打不相识得道理在这群猴子之中得到了完美得体现。那么问题来了,这道题和猴王有半毛钱的关系呀!!!!!
虽然与猴王没有任何关系,但是这题呢还是要做,这算法呢还是要讲。看到这道题的第一眼,我就觉得它不是什么好东西,结果我发现它果然不是什么好东西。这道题到底该怎么做呢?最容易想到的算法有几个,我在这里一个一个列出来并进行一波讲解。

1.堆

堆很容易想到,因为每次我们都是找猴子及其朋友中的最强者来打一架,所以一个大根堆,就可以解决问题。但是每次找两个猴子是不是朋友很麻烦,需要O(n)的时间进行遍历,这就衍生出了第二种算法。

2.堆+并查集

既然只用堆查找需要较长时间,那使用一个并查集来进行查找即可方便快速的查找,但是新的问题又出现了,如何把两个猴子的朋友的强壮值合并在一起呢?要知道合并两个大根堆绝非易事。码代码就可以码得人神魂颠倒。自然是很不科学。

3.二叉搜索树

二叉搜索树运用到这个题上也并非不可,因为二叉搜索树,本身建树的过程有一定的规律性,即大于根节点的往右走,小于根节点往左走,这就使得它可以更简单的合并,但是时间上还是伤不起。

4.终极算法——可并二叉堆(当然是可以用Fibonacci堆,但实在是太难写了,博主完全不会)

利用可并堆,对两个堆用O(log n)的时间去合并,用O(1)的时间进行查找,可以达到我们预想的效果。在下面会有详细的讲解。

可并堆 名字里面有堆,说明这还是一个堆,但是它拥有合并的功能。可并堆同时又叫做左偏树,说明他本身的形状很像一棵树,可以像树一样进行遍历与查找,并且左偏、左偏,这棵树以左儿子为重。所以我们建树的时候就要往左边偏,当然你要往右边偏也没人拦得了你。

这里说一下,左偏树的几个基本概念。
1.定义一棵左偏树中的外结点为左子树或右子树为空的结点为外结点。
2.定义结点i的距离dist[i]为结点i到它后代中最近的外结点所经过的边数。
3.左偏性质:一颗左偏树中任意结点的左子结点的距离不小于右子结点的距离

左偏树有几种基本操作:

1.合并

int Merge(int a,int b)
{
    if(a==NULL) return b;
    if(b==NULL) return a;
    if(Tree[a].data<Tree[b].data) swap(a,b);
    Tree[a].rchild=Merge(Tree[a].rchild,b);
    if(Tree[a].rchild!=NULL) Tree[Tree[a].rchild].father=a;
    if(Tree[a].lchild!=NULL) Tree[Tree[a].lchild].father=a;
    if(Tree[Tree[a].lchild].dist<Tree[Tree[a].rchild].dist) swap(Tree[a].lchild,Tree[a].rchild);
    if(Tree[a].rchild==NULL) Tree[a].dist=0;
    else Tree[a].dist=Tree[Tree[a].rchild].dist+1;
    return a;
}
/*dist表示距离,因其左偏性质,我们自然是要在右距离的值比左距离的值大的时候交换一下,由于我们是储存的时候只储存了编号,换一下值就会连带着一起转移掉,所以无所谓了。*/

2.删除

int delet(int p)
{
    Tree[p].data/=2.0;
    int l=Tree[p].lchild;
    int r=Tree[p].rchild;
    Tree[l].father=l;
    Tree[r].father=r;
    Tree[p].dist=Tree[p].lchild=Tree[p].rchild=NULL;
    return Merge(l,r);
}
/*删除的时候就把根节点去掉,把它左儿子的父亲变为它自己,右儿子的父亲也变为它自己,合并左右两棵子树,再合并一下根节点。搞定!!!*/

下面放出猴王的代码:

//由于是道裸题就不在阐述太多,只有一些微弱的注释供诸君观赏
#include<cstdio>
#include<cstdlib>
#include<algorithm>
using namespace std;
const int maxm=200010;
struct node{
    int lchild,rchild;
    int dist,father;
    int data;
    node()
    {
        lchild=NULL;
        rchild=NULL;
        dist=0;
        data=0;
        father=NULL;
    }
}Tree[maxm];//简单易懂的数组名
int n,m;
int inline readin() //蒟蒻的读入优化QAQ
{
    int x=0;
    char c=getchar();
    while(c>'9'||c<'0') c=getchar();
    while(c>='0'&&c<='9')
    {
        x=x*10+c-'0';
        c=getchar();
    }
    return x;
}
int Merge(int a,int b)//合并函数
{
    if(a==NULL) return b;
    if(b==NULL) return a;
    if(Tree[a].data<Tree[b].data) swap(a,b);
    Tree[a].rchild=Merge(Tree[a].rchild,b);
    if(Tree[a].rchild!=NULL) Tree[Tree[a].rchild].father=a;
    if(Tree[a].lchild!=NULL) Tree[Tree[a].lchild].father=a;
    if(Tree[Tree[a].lchild].dist<Tree[Tree[a].rchild].dist) swap(Tree[a].lchild,Tree[a].rchild);
    if(Tree[a].rchild==NULL) Tree[a].dist=0;
    else Tree[a].dist=Tree[Tree[a].rchild].dist+1;
    return a;
}
int delet(int p)//删除函数
{
    Tree[p].data/=2.0;
    int l=Tree[p].lchild;
    int r=Tree[p].rchild;
    Tree[l].father=l;
    Tree[r].father=r;
    Tree[p].dist=Tree[p].lchild=Tree[p].rchild=NULL;
    return Merge(l,r);
}
int search(int x)//其实可以用并查集,让程序跑得更快,但我懒得写了
{   
    if(Tree[x].father==x)
        return x;
    search(Tree[x].father);
}
void work()
{
    int A,B,p,q;
    for(int i=1;i<=m;i++)
    {
        int x,y;
        x=readin(),y=readin();
        p=search(x);
        q=search(y);
        A=delet(p);//A为删除后左右子树合并的根节点
        B=delet(q);//B同A
        A=Merge(A,p);//合并A与改变了值的根节点
        B=Merge(B,q);//合并B与改变了值的根节点
        A=Merge(A,B);//合并全新的A B
        printf("%d\n",Tree[A].data);
    }
}
int main()
{
    n=readin();
    for(int i=1;i<=n;i++)
    {
        Tree[i].data=readin();
        Tree[i].father=i;
    }
    m=readin();
    work();
}

这里写图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值