【HDU1512】Monkey King-左偏树+并查集(左偏树入门题)

测试地址:Monkey King

题目大意:有N(N≤100000)只猴子,标号为1~N,每只猴子有一个强壮值,一开始他们互相之间都不认识,但猴子避免不了争吵,它们之间会进行M(M≤100000)次争吵,每次争吵都是标号为a和b的两只互不认识的猴子相互争吵,争吵过后,他们就会分别去找他们所认识的最强壮(强壮值最大)的猴子来决斗,决斗过后决斗的两只猴子的强壮值减半(向下取整),但打斗过后两边的猴子就都会互相认识,互相认识的猴子之间就不会再进行争吵了,你的任务就是对于每次争吵,如果不发生争吵(即两方认识)输出-1,否则输出决斗后两方猴子中最强壮猴子的强壮值。

做法:初观察这一题,维护互相认识的关系可以用并查集O(N)解决,维护最大的强壮值似乎可以用堆来解决,但是两方猴子互相认识之后,两个堆之间需要合并,如果将一个堆中的元素一个一个插入另一个堆中,复杂度是O(N^2)的,不能接受,这时候就要引出可并堆这个数据结构了,而左偏树则是可并堆的一种实现方法,关于左偏树的教程网上很多这里就不讲了。于是这里我们维护很多个大根堆,每次决斗,删除两个堆的根节点,将它们的强壮值减半,然后再合并回原来的堆中,再将两个堆合并,输出根节点的强壮值即可。并查集可以在合并和删除的过程中一并维护。

以下是本人代码:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
int n,m,fa[100010];
struct leftisttree
{
  int lc,rc,key,dis;
}nd[100010];

int find(int x) //并查集中的查找
{
  int r=x,i=x,j;
  while(fa[r]!=r) r=fa[r];
  while(i!=r) j=fa[i],fa[i]=r,i=j;
  return r;
}

int merge(int x,int y) //合并堆并返回合并后堆的根
{
  if (!x) return y;
  if (!y) return x;
  if (nd[x].key<nd[y].key) swap(x,y);
  nd[x].rc=merge(nd[x].rc,y);
  int l=nd[x].lc,r=nd[x].rc;
  fa[r]=x;
  if (nd[l].dis<nd[r].dis) swap(nd[x].lc,nd[x].rc);
  nd[x].dis=nd[nd[x].rc].dis+1;
  return x;
}

int del(int x) //删除一个堆的根节点并返回左右子树合并后的根
{
  int l,r;
  l=nd[x].lc,r=nd[x].rc;
  fa[l]=l,fa[r]=r;
  nd[x].lc=nd[x].rc=nd[x].dis=0;
  return merge(l,r);
}

void solve(int x,int y) //求解决斗后最大的强壮值
{
  int l,r;
  nd[x].key/=2;nd[y].key/=2;
  l=del(x),r=del(y);
  l=merge(l,x),r=merge(r,y);
  l=merge(l,r);
  printf("%d\n",nd[l].key);
}

int main()
{
  while(scanf("%d",&n)!=EOF)
  {
    nd[0].lc=nd[0].rc=0;nd[0].dis=-1;
    for(int i=1;i<=n;i++)
    {
      scanf("%d",&nd[i].key);
      nd[i].lc=nd[i].rc=nd[i].dis=0;
      fa[i]=i;
    }
    scanf("%d",&m);
    for(int i=1,a,b;i<=m;i++)
	{
	  scanf("%d%d",&a,&b);
	  if (find(a)==find(b)) {printf("-1\n");continue;}
	  solve(find(a),find(b));
	}
  }
  
  return 0;
}


posted @ 2017-03-16 17:41  Maxwei_wzj  阅读(97)  评论(0编辑  收藏  举报