http://acm.hdu.edu.cn/showproblem.php?pid=1512
题意:
有n个猴子,,一开始每个猴子只认识自己,每个都有自己的力量
m个操作,每次给x y,表示x和y打架,
先判断x,y是否有同一个老大,如果是,输出-1,不打,
否则,让各自的老大(种群中力量值最大者) 打一架,后果是各自老大力量值减半,从此两个种群合并,仍旧是力量值最大的为新老大。输出新老大的力量值。
思路:
对于记录猴子的种群,就是用并查集,每次让猴子种群中力量最大的来打架,可以用优先队列,但问题是,每次有一个合并的操作,所以优先队列不合适,得用 左偏树,其插入,查询删除(顶点/最值点),都是logn,并且可以合并两棵树,复杂度也是logn,因此符合要求
具体实现见代码注释;
#include <cstdio>
#include <cmath>
#include <cstring>
#include <string>
#include <algorithm>
#include <iostream>
#include <queue>
#include <map>
#include <set>
#include <vector>
using namespace std;
const int maxn3=100010;
int a[maxn3]; //原始数据数组-可有可无
int c[maxn3]; //并查集数组
int tot,v[maxn3],l[maxn3],r[maxn3],d[maxn3];
//v为节点值,l,r为左右节点序号
//定义"外节点"为左或子树为空的节点,d指该点到其外节点最短距离(属于左偏树内容)
int merge(int x,int y) //左偏树合并函数*最关键
{
if (!x)
return (y);
if (!y)
return (x);
if (v[x]<v[y])
swap(x,y);
r[x]=merge(r[x],y);
if (d[l[x]]<d[r[x]])
swap(l[x],r[x]);
d[x]=d[r[x]]+1;
return x;
}
int init(int x) //左偏树初始化新节点函数
{
tot++;
v[tot]=x;
l[tot]=r[tot]=d[tot]=0;
return tot;
}
int top(int x) //左偏树取顶点函数
{
return v[x];
}
int pop(int x) //左偏树pop顶点函数
{
return merge(l[x],r[x]);
}
int find(int x) //并查集查找函数
{
if(x==c[x]) return x;
return c[x]=find(c[x]);
}
int main()
{
// freopen( "1.out","r",stdin ); // scanf 从1.txt输入
// freopen( "test.out","w",stdout ); //printf输出到1.tx
int n,m;
int m1,m2,i;
while( scanf("%d",&n)!=EOF)
{
tot=0;
for (i=1;i<=n;i++)
{
scanf("%d",&a[i]);
init(a[i]); //初始化新节点
c[i]=i; //初始化并查集
}
cin>>m;
for (i=1;i<=m;i++)
{
scanf("%d%d",&m1,&m2);
int f1=find(m1); //并查集查找根节点
int f2=find(m2);
if (f1==f2) //老大相同即不打架
{
printf("-1\n");continue;
}
else
{
int tt1=pop(f1);//去掉根并记录新节点
int tt2=pop(f2); //去掉根并记录新节点
v[f2]/=2; //原根节点减半
v[f1]/=2; //原根节点减半
l[f1]=r[f1]=d[f1]=0; //把原根节点置零
l[f2]=r[f2]=d[f2]=0;
int yy=merge(tt1,tt2); //合并去掉根后的2个新树
int zz=merge(yy,f1); //新树插入两个减半后的原根节点
zz=merge(zz,f2);
c[f1]=zz;//一开始树1都指向f1,现在直接指向最终新根节点即可
c[f2]=zz;
c[zz]=zz;//保证最终根节点指向自己
printf("%d\n",top(zz));//取出最终根节点
}
}
}
return 0;
}