HDU 2473 Junk-Mail Filter 【并查集+设立虚父节点(马甲)】
题目链接:
http://acm.hdu.edu.cn/showproblem.php?pid=2473
原题:
Problem DescriptionRecognizing junk mails is a tough task. The method used here consists of two steps:5 6M 0 1M 1 2M 1 3S 1M 1 2S 33 1M 1 20 0
Case #1: 3Case #2: 2
有N封邮件, 然后又两种操作,如果是M X Y , 表示X和Y是相同的邮件。 如果是S X,那么表示对X的判断是错误的,X是不属于X当前所在的那个集合,要把X分离出来,让X变成单独的一个。
很明显是用并查集来做。 开始让我混淆的一个地方是,假设如下情况
M 0 2
M 1 2
S 2
那么按照并查集来做, 0指向2, 1指向2,即
0 -->2
1 -->2 ,
那么删除2之后,我以为题目意思是所有与2有关系的都要删除, 那么这两个关系都要去掉, 又变成独立的3个了。
但是我这种理解是错的。 合并起来后就是一个集合{0,1,2}, 如果把2删除掉之后, {0,1}还是集合。
理解题意之后, 我们知道用并查集来构造集合是很容易的,但是要把集合中的一个删掉,却很不容易。 通过这题,我学习到了所谓的设立需父节点的方法。
关键的过程是假设要删除x点, 那么不是真的删除x点, 而是通过一个映射(这里用数组majia[N]),把x变成一个新的点即majia[x] = newNode.那么, 原来的那些集合还是不变,只是少了个x点。
代码:
#include<cstdio>#include<cstring>#define N 1100000int f[N],rank[N],majia[N],flag[N],id,n,m;inline void init(){ for(int i=0; i<n; ++i) f[i]=majia[i]=i; memset(rank, 0, sizeof(rank)); id=n;}int find(int x){ int i, j=x; while(j!=f[j]) j=f[j]; while(x!=j){i=f[x];f[x]=j;x=i;} return j;}void Union(int x,int y){ int a=find(x), b=find(y); if(a==b)return ; if(rank[a]>rank[b]) f[b]=a; else{ if(rank[a]==rank[b]) ++rank[b]; f[a]=b; }}void Delete(int x){ f[id]=id; majia[x]=id++;}int main(){ char cmd[3]; int a,b,cas=1; while(~scanf("%d%d",&n,&m)&&n+m){ init(); for(int i=0; i<m; ++i){ scanf("%s",cmd); if(cmd[0]=='M'){ scanf("%d%d",&a,&b); Union(majia[a],majia[b]); } else{ scanf("%d",&a); Delete(a); } } memset(flag, 0, sizeof(flag)); int ans=0; for(int i=0; i<n; ++i){ a=find(majia[i]); if(!flag[a]){ ++ans; flag[a]=1; } } printf("Case #%d: %d\n",cas++, ans); } return 0;}—— 生命的意义,在于赋予它意义。
原创 http://blog.csdn.net/shuangde800 , By D_Double (转载请标明)