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