HDU 3635 Dragon Balls(并查集:路径压缩)
http://acm.hdu.edu.cn/showproblem.php?pid=3635
题意:
这里有N个龙珠,每个龙珠初始时都在本身的城市(第i个龙珠在第i个城市),然后要你回答对应的询问。
输入:首先是一个T(0< T <= 100),表示由T个实例。对于每个实例,第一行是N and Q (2 < N <= 10000 , 2 < Q <= 10000),表示接下来有Q个询问。然后对应每个询问是下面两个命令的一种:
T A B(1<= A, B <= N),表示将和A龙珠在同一个城市的所有龙珠都移到B龙珠所在的城市去。(保证A与B不在同一个城市)
Q A。你需要输出X,Y,Z。X是A龙珠目前所在的城市,Y是X城市所有的龙珠,Z是A龙珠被转移的次数。
输出:回答每条Q命令的X,Y,Z.
分析:
其实这就是一个并查集的应用。每个龙珠对应一个并查集的初始节点,然后用每个连通分量的根节点来维护num[i](即当前分量中的节点总数),fa[i]表第i个节点的父节点编号。dist[i]表第i个节点到其父节点的距离(即第i个节点一共被转移了几次才连接上它当前的父节点的)
龙珠被转移的次数就是该龙珠距离根节点的距离。初始dist[i]为0.每当合并两个连通分量的时候,被合并的连通分量的dist值要置为1。执行findset操作时,子节点的dist要加上父节点的dist值。
现在有个问题:如果2龙珠被转移了50次且3龙珠被转移了50次,现在执行T 2 3命令,是不是执行了命令之后2龙珠会被认为转移了101次呢?
不会的,因为2龙珠就算被转移到了3龙所在的城市,2龙珠也不是挂在3龙珠下面,而是3龙珠所属连通分量根节点下面,这个根节点一定是被转移了0次的。
如果我们要求i节点被转移了几次才被转移到当前连通分量的,我们只需要先findset(i)操作,然后dist[i]的值就是我们所求。(想想为什么)
AC代码(新):
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int maxn=10000+5; int fa[maxn]; //父节点 int num[maxn]; //节点数 int dist[maxn]; //转移次数 int findset(int x) { if(fa[x]==-1) return x; int root=findset(fa[x]); dist[x] += dist[fa[x]]; return fa[x]=root; } int bind(int u,int v)//将u分量加入v分量下面 { int fu=findset(u); int fv=findset(v); if(fu != fv) { num[fv] += num[fu]; dist[fu]++; fa[fu]=fv; return 1; } return 0; } int main() { int T; scanf("%d",&T); for(int kase=1; kase<=T; ++kase) { printf("Case %d:\n",kase); int n,q; scanf("%d%d",&n,&q); for(int i=1;i<=n;i++) { fa[i]=-1; num[i]=1; dist[i]=0; } char str[10]; while(q--) { scanf("%s",str); if(str[0]=='T') { int u,v; scanf("%d%d",&u,&v); bind(u,v); } else if(str[0]=='Q') { int u; scanf("%d",&u); int root=findset(u); printf("%d %d %d\n",root,num[root],dist[u]); } } } return 0; }
AC代码:375ms
#include<cstdio> using namespace std; const int MAXN=10000+100; int pa[MAXN];//pa[i]表示节点i所属的连通分量,初始为-1(为-1时,表示i自己就是顶点) int d[MAXN];//d[i]表示i节点到根的距离,初始为0 int num[MAXN];//num[i]表示第i个连通分量有多少个节点 int findset(int x)//返回x所属的连通分量且更新d[x]为x到根的距离 { if(pa[x]==-1)return x; int root= findset(pa[x]); d[x] += d[pa[x]];//注意这里 return pa[x] = root; } void bind(int i,int j) { int fa=findset(i); int fb=findset(j); if(fa!=fb) { pa[fa]=fb; num[fb]+=num[fa]; d[fa]=1;//注意这里 } } int main() { int T,kase=1; scanf("%d",&T); while(T--) { printf("Case %d:\n",kase++); int n,q; scanf("%d%d",&n,&q); for(int i=1;i<=n;i++)//初始化 { pa[i]=-1; d[i]=0; num[i]=1; } while(q--) { char str[10]; scanf("%s",str); if(str[0]=='T') { int a,b; scanf("%d%d",&a,&b); bind(a,b); } else if(str[0]=='Q') { int a; scanf("%d",&a); int st=findset(a); printf("%d %d %d\n",st,num[st],d[a]); } } } return 0; }
new AC code:
#include<cstdio> #include<cstring> using namespace std; const int maxn=10000+5; int fa[maxn]; //所属连通分量的根 int dist[maxn];//当前节点与根的距离(即被转移的次数) int num[maxn]; //当前连通分量所具有的节点数 int findset(int x) { if(fa[x]==-1)return x; int root=findset(fa[x]); dist[x] += dist[fa[x]]; return fa[x]=root; } void bind(int u,int v) { int fu=findset(u); int fv=findset(v); if(fu!=fv) { fa[fu]=fv; dist[fu]=1; num[fv]+=num[fu]; } } int main() { int T; scanf("%d",&T); for(int kase=1;kase<=T;kase++) { printf("Case %d:\n",kase); int n,m; scanf("%d%d",&n,&m); for(int i=1;i<=n;i++)//初始化 { fa[i]=-1; dist[i]=0; num[i]=1; } while(m--) { char type; int u,v; scanf(" %c",&type); if(type=='T') { scanf("%d%d",&u,&v); bind(u,v); } else if(type=='Q') { scanf("%d",&u); int fu=findset(u); printf("%d %d %d\n",fu,num[fu],dist[u]); } } } return 0; }