并查集的删除---WOJ 1403: Quick Answer
连续想了好几天的题=。=,晚上跟人讨论过后终于AC了.
在本题中,需要删除一些元素,而又不希望被删除的元素(记为d)的改变影响到其他和 d 在同一组的元素(比如d是所有同组元素的祖先)
因此可以另外开一个数组 pid[], pid[i]存放的是元素 i 的编号,这个编号在程序运行过程中是逐渐变化的。
当删除了 元素 i 以后,将元素 i 的编号更改为当前最大的编号+1(新的pid[i]),再把parent[i]赋值为新的pid[i]
这样可以使得元素 i 原先的同组元素不受影响,且元素 i 被分配到一个只包含元素 i 的新组。
以上是来自WOJ的提示,当初看这个时,一直对pid[]有疑惑,多亏了hnu的朋友指点后才找到了错误。
对于这题,我的想法是,用use[i]数组标记点 i 是否被删除或没与其它点相连过。
用pid[i]记录点 i 的所属集的编号;
判断时,先看是否为被删除的点,是的话直接no,其次再根据x,y所属集的根编号判断x,y是否在同一集中。
代码如下:
int bin[20001],pid[20001];
bool use[20001];
int findx(int x) /* 返回所属团队(编号) */
{
int r=pid[x]; /* 关键点,用pid[x]表示x编号 */
while(bin[r]!= r)
r=bin[r];
return r;
}
void merge(int x,int y) /* 将x,y并到同一集合中 */
{
int fx,fy;
fx = findx(x);
fy = findx(y);
bin[fy] = fx;
}
int main()
{ char ch[11];
int n,i,N1=0,N2=0,x,y,fx,fy;
// freopen("2.txt","r",stdin);
while(scanf("%d",&n)!=EOF)
{ memset(use,false,sizeof(use));
for(i=0;i<=n;i++)
{ bin[i]=i; /* 初始时,pid[i]和bin[i]都赋为i */
pid[i]=i;
}
while(scanf("%s",ch)&&ch[0]!='e')
{ if(ch[0]=='c')
{ scanf("%d%d",&x,&y);
use[x]=true;
use[y]=true;
merge(x,y);
}
if(ch[0]=='q')
{ scanf("%d%d",&x,&y);
if((!use[x]||!use[y])&&x!=y) // x!=y 初始时use全为false,当没有相连点直接q 1 1时,需要跳过use判断.
N2++;
else
{ fx=findx(x);
fy=findx(y);
if(fx==fy)
N1++;
else
N2++;
}
}
if(ch[0]=='d')
{
scanf("%d",&x);
use[x]=false; /* 删除点x,使x自成一组,其编号为当前最大编号+1 */
pid[x]=++n;
bin[n]=n;
}
}
printf("%d , %d/n",N1,N2);
N1=0;N2=0;
}
return 0;
}
在hnu跑了78MS,
将深度小的树合并到深度大的树,将效率优化到0(logN)对速度没有明显优化.
http://hi.baidu.com/abs_rember/blog/item/de0436c5bfcf08d5d00060ad.html