[NOI2002]银河英雄传说
银河英雄传说
这是一道带权并查集的题目。
首先,按照题目要求,我们可以很容易的想到用并查集来实现。但是我们会发现,如果只用并查集记录队列的合并情况,那么就无法满足C操作,所以我们需要在维护并查集的同时,维护每个结点的信息。
这道题目需要查询是否在一个队列中,也就是是否在一个集合中,是并查集的基本操作,写一个find函数就可以了。但是第二个要求是一个并查集中两个结点的距离,那么我们就需要一个d数组,记录结点i到fa[i]的距离,那么询问的时候就可以输出abs(d[x]-d[y])-1。
那接下来的问题就是怎么维护这个d数组呢?首先我们可以明确的一点是需要在查询、合并并查集的时候维护,那我们想一想,并查集在合并的时候,会把一个并查集的头结点和另一个并查集的头结点合并,但是题目的要求是,把一个并查集的头部接到另一个并查集的尾部,所以我们就需要知道并查集的大小,我们用siz表示,这样的话每次合并我们就只需要做和就可以了,因为结点到新队列头结点的距离=当前节点原来到队头的距离+新并查集的大小。如果不理解的话,我们可以看一下下面的图:
当我们要合并x和y的时候,我们会把两个并查集的头结点连接起来,所以x中子节点的d就可以表示为y的大小+它到x头结点的距离。
下面是AC代码:
#include<bits/stdc++.h> #define R register int #define M 500500 using namespace std; int t,fa[M],d[M],siz[M]; //d 表示飞船i到队头的距离 siz 表示飞船所在并查集的大小 char ch; inline int find(int x){ if(fa[x]!=x){ int k=fa[x]; fa[x]=find(fa[x]); d[x]+=d[k]; //当前结点到新队列头结点的距离=当前节点原来到队头的距离+队头到新队列头的距离 siz[x]=siz[fa[x]]; //更新当前集合的大小 } return fa[x]; } inline void add(int x,int y){ int fx=find(x),fy=find(y); fa[fx]=fy; d[fx]=d[fy]+siz[fy]; //前结点到新队列头结点的距离=当前节点原来到队头的距离+新并查集的大小 siz[fy]+=siz[fx]; siz[fx]=siz[fy]; } inline int query(int x,int y){return find(x)!=find(y) ? -1 : abs(d[x]-d[y])-1;} int main(){ ios::sync_with_stdio(0); int x,y; cin>>t; for(R i=1;i<=30000;++i) fa[i]=i,siz[i]=1; while(t--){ cin>>ch>>x>>y; if(ch=='M') add(x,y); else cout<<query(x,y)<<endl; } return 0; }