zoj3261 带权并查集

题意:有很多颗星球,各自有武力值,星球间有一些联系通道,现在发生战争,有一些联系通道会被摧毁,而一些星球会通过还没有被摧毁的联系通道直接或者间接联系能够联系到的武力值最高的星球求救,如果有多个武力值都为最高的,那就联系一个编号最小的。现在给出一系列求救和摧毁的序列,一次执行,并对于每一个求救指令寻找合适的求救星球编号,如果没有可以求救的则输出 -1;

由于一般并查集只能够合并集合而不能将集合拆离,因此可以离线之后反向执行,这样集合的拆离就变成了集合的合并。

只要先保存所有的边和询问,然后对于所有的摧毁联系用建边的方式记录,然后扫一遍一开始的联系通道关系,除了将会被摧毁的边不动外,将剩下的联系通道用并查集的方式建立起来。

由于需要查找的值是武力值最大并且编号最小的,所以可以在合并并查集的时候根据武力值大小,将武力值小的那个节点的父亲设置为祖先大的,如果需要合并的两个节点的武力值是相等的,那么把编号大的节点的父亲设置为编号小的节点,这样在查询祖先的时候就一定查到武力值最大并且编号最小的节点了。

然后对于所有命令序列反向处理,从最后一条开始,查询就直接输出结果,如果查到的祖先节点是它自己就输出 -1,如果是摧毁通道那就建立这两点之间的通道就行了。

 #include<stdio.h>
#include<string.h> int fa[],n,a[],ans[];
int l1[],l2[],y[],z[];
bool x[];
int head[],nxt[],point[],size;
char s[]; void add(int a,int b){
point[size]=b;
nxt[size]=head[a];
head[a]=size++;
point[size]=a;
nxt[size]=head[b];
head[b]=size++;
} int mmax(int a,int b){
return a>b?a:b;
} void init(){
for(int i=;i<n;i++)fa[i]=i;
} int find(int x){
int r=x,t;
while(r!=fa[r])r=fa[r];
while(x!=r){
t=fa[x];
fa[x]=r;
x=t;
}
return r;
} int main(){
int c=;
while(scanf("%d",&n)!=EOF){
if(c++)printf("\n");
int i,m;
size=;
memset(head,-,sizeof(head));
for(i=;i<n;i++)scanf("%d",&a[i]);
init();
scanf("%d",&m);
for(i=;i<=m;i++)scanf("%d%d",&l1[i],&l2[i]);
int q;
scanf("%d",&q);
for(i=;i<=q;i++){
scanf("%s",s);
if(s[]=='q'){
x[i]=;
scanf("%d",&y[i]);
}
else{
x[i]=;
scanf("%d%d",&y[i],&z[i]);
add(y[i],z[i]);
}
}
int j;
bool f;
for(i=;i<=m;i++){
f=;
for(j=head[l1[i]];~j;j=nxt[j]){
if(point[j]==l2[i]){f=;break;}
}
if(!f)continue;
int x1=find(l1[i]),y1=find(l2[i]);
if(x1!=y1){
if(a[x1]>a[y1])fa[y1]=x1;
else if(a[x1]<a[y1])fa[x1]=y1;
else if(x1<y1)fa[y1]=x1;
else fa[x1]=y1;
}
}
int cnt=;
for(i=q;i>=;i--){
if(x[i]){
int x1=find(y[i]),y1=find(z[i]);
if(x1!=y1){
if(a[x1]>a[y1])fa[y1]=x1;
else if(a[x1]<a[y1])fa[x1]=y1;
else if(x1<y1)fa[y1]=x1;
else fa[x1]=y1;
}
}
else{
int x1=find(y[i]);
ans[++cnt]=a[x1]>a[y[i]]?x1:-;
}
}
for(i=cnt;i>=;i--){
printf("%d\n",ans[i]);
}
}
return ;
}
上一篇:SQL Server调优系列进阶篇(如何维护数据库索引)


下一篇:Linux下apache的停止、开启、重启