转载请注明出处:http://blog.csdn.net/u012860063
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2473
5 6 M 0 1 M 1 2 M 1 3 S 1 M 1 2 S 3 3 1 M 1 2 0 0
Case #1: 3 Case #2: 2
题意:一些点M a,b代表a,b是一个集合,S a代表从集合中删除a点,最后输出有几类
思路:并查集,M代表合并,S代表删除,下面讲一下删除操作
大家都知道合并操作就是找到找到两个节点的父亲,修改父亲,如果删除就是将该点的父亲重新设置成自己,这样行不行呢?
这是不行的,比如1,2,3的父亲都是1,现在删除1,1的父亲还是1,2,3也是1,集合还是1个,正确的应该是2个。
那删除节点的父亲不设成自己给新申请一个节点当做父亲,比如1,2,3的父亲都是1,在一个集合,现在删除1,申请了4当做1的父亲,2,3父亲都是1,然后Find(2)找2的父亲
2的父亲是1,但是1的父亲是4,所以给2的父亲更新成了4,3同理,所以还不行。
正确的方法是每一个点都设立一个虚拟父亲比如1,2,3的父亲分别是4,5,6,现在合并1,2,3都在一个集合,那他们的父亲都是4,现在删除1,那就给1重新申请一个节点7
现在2,3的父亲是4,1的父亲是7,删除成功。
(此思路详解为转载)#include <iostream>
#include <cstring>
using namespace std;
#define N 1000047
int father[N], flag[N];
int n, m, ID;
int Find(int x)
{
return father[x]==x?x:father[x]=Find(father[x]);
}
void Union(int x, int y)
{
int f1 = Find(x);
int f2 = Find(y);
if(f1 != f2)
father[f2] = f1;
}
void init()
{
int i;
for(i = 0; i < n; i++)
{
father[i] = i+n;//虚拟父亲
}
for(i = n; i <= n+n+m; i++)
{//n+n+m: 最多可能删除m个节点
father[i] = i;
}
}
void Delet(int x)
{
father[x] = ID++;
}
int main()
{
int i, j, a, b, temp;
int cas = 0;
char c[5];
while(~scanf("%d%d",&n,&m))
{
ID = n+n;
init();
if(n == 0 && m == 0)
break;
for(i = 0; i < m; i++)
{
scanf("%s",c);
if(c[0] == 'M')
{
scanf("%d%d",&a,&b);
Union(a,b);
}
else if(c[0] == 'S')
{
scanf("%d",&temp);
Delet(temp);
}
}
int cont = 0;
memset(flag,0,sizeof(flag));
for(i = 0; i < n; i++)
{
int x = Find(i);
if(flag[x] == 0)//没有共同父亲
{
cont++;
flag[x] = 1;
}
}
printf("Case #%d: %d\n",++cas,cont);
}
return 0;
}
hdu2473 Junk-Mail Filter(并查集(虚拟父亲)+删点),布布扣,bubuko.com
hdu2473 Junk-Mail Filter(并查集(虚拟父亲)+删点)
原文地址:http://blog.csdn.net/u012860063/article/details/36667069