并查集是个很实用的数据结构,主要功能可以动态维护若干个不重叠的集合,并支持合并与查询,当然这些都只是概念,个人理解并查集能够维护具有传递性的关系,还可有维护节点之间的连续性,克鲁斯卡尔就是这样做到的.普通的并查集就是如此,今天介绍并查集的扩展,看题:
题目一看便知道要用到并查集,判断在不在一列可以普通的并查集就能实现,可是如何实现了解两艘战舰之间的距离呢?
我刚开始也是直接暴力枚举,加上一个son数组和dis数组,dis表示点到点所在树的根的距离,每次合并两个树时,暴力修改。
最后辛辛苦苦把代码写完,就得了50,以下是暴力代码:
#include<bits/stdc++.h> using namespace std; const int maxn=31000; int f[maxn],t,son[maxn],dis[maxn]; char ch; inline int read() { int x=0,ff=1; char ch=getchar(); while(!isdigit(ch)) { if(ch=='-') ff=-1; ch=getchar(); } while(isdigit(ch)) { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); } return x*ff; } inline void put(int x) { if(x<0) putchar('-'),x=-x; if(x>9) put(x/10); putchar(x%10+'0'); } inline int getf(int x) { if(f[x]==x) return x; else return (getf(f[x])); } inline int gets(int x) { if(son[x]==x) return x; else return (gets(son[x])); } inline void work1(int tou,int wei) { f[tou]=wei;son[wei]=tou; int i=tou; while(1) { if(i==son[i]) {dis[i]=dis[f[i]]+1;break;} dis[i]=dis[f[i]]+1; i=son[i]; } } int main() { //freopen("1.in","r",stdin); t=read(); for(int i=1;i<=30000;i++) f[i]=i,son[i]=i; for(int i=1;i<=t;i++) { cin>>ch; int x=read(),y=read(); int t1=getf(x),t2=gets(x); int t3=getf(y),t4=gets(y); if(ch=='M') work1(t1,t4); if(ch=='C') { if(t1==t3) cout<<abs(dis[x]-dis[y])-1<<endl; else cout<<-1<<endl; } } return 0; }View Code
实在不行就去看了算法进阶,才知到不需维护各个点到根的距离,那样复杂度太高,可以维护每个点与它f[x]的权值(也可理解为从根开始的编号,根不算在内),为什么呢,因为这样你在找父亲与路径压缩时就可以顺便把点x到根的距离解决,因为每次路径压缩都是将路上各点单独拿出来与根相连,距离也就可以解决。找父亲代码:
inline int getf(int x) { if(f[x]==x) return x; //找到根,返回。 int root=getf(f[x]); //找根。 dis[x]+=dis[f[x]]; //因为此时经过回溯,dis[f[x]]的距离已经是 f[x]到根的距离了,所以 return f[x]=root; //dis[x]只需将原本与父亲相连的权值加上父亲的距离即是与跟的距离。 } //最后加上路径压缩.
解决了找父亲,接下来就是合并了,两个子树合并,需要求出x的到y的距离(x,y为两个子树的根,且x要到y上),显然,dis[s]便是y的size(),所以还需维护以个size()数组表示各个集合的点数量,
代码:
inline void merge(int x,int y) { f[x]=y; dis[x]=size[y]; size[y]+=size[x]; }
把这些解决了,题目也就没那么难了,总代码:
#include<bits/stdc++.h> using namespace std; const int maxn=31000; int f[maxn],t,dis[maxn],size[maxn]; char ch; inline int read() { int x=0,ff=1; char ch=getchar(); while(!isdigit(ch)) { if(ch=='-') ff=-1; ch=getchar(); } while(isdigit(ch)) { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); } return x*ff; } inline void put(int x) { if(x<0) putchar('-'),x=-x; if(x>9) put(x/10); putchar(x%10+'0'); } inline int getf(int x) { if(f[x]==x) return x; //找到根,返回。 int root=getf(f[x]); //找根。 dis[x]+=dis[f[x]]; //因为此时经过回溯,dis[f[x]]的距离已经是 f[x]到根的距离了,所以 return f[x]=root; //dis[x]只需将原本与父亲相连的权值加上父亲的距离即是与跟的距离。 } //最后加上路径压缩. inline void merge(int x,int y) { f[x]=y; dis[x]=size[y]; size[y]+=size[x]; } int main() { freopen("1.in","r",stdin); t=read(); for(int i=1;i<=30000;i++) f[i]=i,size[i]=1; for(int i=1;i<=t;i++) { cin>>ch; int x=read(),y=read(); int t1=getf(x); int t2=getf(y); if(ch=='M') merge(t1,t2); if(ch=='C') { if(t1==t2) put(abs(dis[x]-dis[y])-1),cout<<endl; else put(-1),cout<<endl; } } return 0; }View Code