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

来源:互联网 发布:已知矩阵的秩求未知数 编辑:程序博客网 时间:2024/06/06 17:40

测试地址: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;}


0 0