题解 SP5150 【JMFILTER - Junk-Mail Filter】
并查集删点模板题
合并方法与普通并查集一样
删点方法:造假点;
因为我们不知道要删除的点是根节点还是叶节点,所以如果直接删除(用其他点替换)可能会影响到其他节点,于是我们得到了做法:
全部修改,让构造的时候就没有使用真点,及一直用假点来代替,删点的时候只需更改原来的点指向的假点即可,即为造假点。
思路有两种:
-
在父节点数组中设
0~n-1为原来的点,n~2*n-1为假点,2*n~2*n+m为删点时用的,即备用假点(这种方法我一直没改对,不放代码了) -
开两个数组,一个是原来的点,一个是用来标记点对应假点。
至于怎么判断有多少个集合:
扫一遍所以点,找到每一个点的祖宗,如果没有被找到,答案加一并标记,否则不做处理(即找有多少种祖宗)。
PS:大数据请使用较快的读入方式。
发现好多大佬放到标程都没有注释哎,我太菜,只好写点注释(完美的逻辑。。。)。
代码:
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
int f[1000010],id[1000010],n,m,x,y,ans,cnt,ji;//f是父节点,id是对应的假点,数组开大一点(
char a[3];
bool b[1000010];
int find(int x)
{
if(f[x]==x)
return x;
return f[x]=find(f[x]);
}//寻找祖宗+压缩路径
int read()
{
int s = 0, f = 1;
char ch = getchar();
while(!isdigit(ch)) {
if(ch == '-') f = -1;
ch = getchar();
}
while(isdigit(ch)) {
s = s * 10 + ch - '0';
ch = getchar();
}
return s * f;
}
int main()
{
while(n=read(),m=read(),n|m)
{
cnt=n;
for(int i=1;i<=n;i++)
f[i]=id[i]=i;//初始化
for(int i=1;i<=m;i++)
{
scanf("%s",a+1);
if(a[1]=='M')
{
int x=read()+1,y=read()+1;//把数组向后移,变成1~n,便于思考
int fx=find(id[x]),fy=find(id[y]);//找到对应假点的祖宗
if(fx!=fy)
f[fy]=fx;//合并
}
else
{
int x=read()+1;
id[x]=++cnt;//把对应点的指针指向一个新的点(相当于删除了)
f[id[x]]=id[x];//初始化新点的父节点指针
}
}
memset(b,0,sizeof(b));//重置记录答案的部分
ans=0;
for(int i=1;i<=n;i++)
{
int qwq=find(id[i]);
if(!b[qwq])//统计答案
{
ans++;
b[qwq]=1;
}
}
printf("Case #%d: %d\n",++ji,ans);
}
return 0;
}