测试地址: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
",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
");continue;}
solve(find(a),find(b));
}
}
return 0;
}