题目大意:每次取出两个大根堆的对顶,将这两个节点的键值减半,维护堆的性质,然后将这两个大根堆合并起来,输出堆顶的键值。
很明显,这题的大部分时间都用来合并两个大根堆了,所以我们不能用普通的二叉堆,因为普通的二叉堆合并的时间复杂度是O(n)的。
考虑到合并的时间复杂度应该降低,我们就想到了左偏树(当然,斜堆、二项堆、斐波那契堆也是可以的),合并操作的时间复杂度是O(logn)的。
然后,就是一些简单的左偏树合并操作了。
附上AC代码:
#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
#define N 100010
using namespace std;
int n,w[N],m,f[N],ls[N],rs[N],dis[N],x,y;
bool read(int& a){
static char c=getchar();a=0;int f=1;
while (!isdigit(c)) {
if (c==EOF) return 0;
if (c=='-') f=-1;
c=getchar();
}
while (isdigit(c)) a=a*10+c-'0',c=getchar();
return 1;
}
int gf(int x){
while (f[x]) x=f[x];
return x;
}
int hb(int a,int b){
if (a*b==0) return a+b;
if (w[a]<w[b]) swap(a,b);
rs[a]=hb(rs[a],b);
f[rs[a]]=a;
if (dis[ls[a]]<dis[rs[a]]) swap(ls[a],rs[a]);
dis[a]=dis[ls[a]]+1;
return a;
}
int main(void){
while (read(n)){
for (int i=1; i<=n; ++i) read(w[i]);
memset(f,0,sizeof f),memset(ls,0,sizeof ls),memset(rs,0,sizeof rs),memset(dis,0,sizeof dis);
read(m),dis[0]=-1;
while (m--){
read(x),read(y);
x=gf(x),y=gf(y);
if (x==y) puts("-1");
else {
f[ls[x]]=f[rs[x]]=f[ls[y]]=f[rs[y]]=0;
int h=hb(hb(ls[x],rs[x]),hb(ls[y],rs[y]));
ls[x]=rs[x]=0,w[x]>>=1,h=hb(h,x);
ls[y]=rs[y]=0,w[y]>>=1,h=hb(h,y);
printf("%d\n",w[h]);
}
}
}
return 0;
}