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; }