题解:SP5150 JMFILTER - Junk-Mail Filter(并查集学习笔记))

SP5150 题解

题面

原题传送门。

前置知识

  1. 并查集。
  2. 拥有 SPOJ 账号。

思路

首先这题很容易想到是并查集。(没学过的看后记。)

对于一个点 i,我们可以直接设 paii 的父节点。(跟的父节点设为自己。)

然后就是查询一个元素属于哪个集合的操作。(这很基础。)

long long find(long long x){
    if(pa[x]==x) return x;
    return pa[x]=find(pa[x]);//路径压缩,pa直接赋值为代表元素。 
}

接着,对于合并的操作,也是基础的,没有改动。

只要让 x 的树根作为 y 的树根的子节点。

void merge(long long x, long long y) {
    pa[find(x)]=find(y);
}

接下来就是初始化操作了,我们发现有了删除操作后,就需要“假点”这个东西,就是 pa0,1,2,,n1 为真点,pan,n+1,n+2,2×n1 为假点,pa2×n,2×n+1,,2×n+m 是为了统计删除而存在的点。

void init(long long n, long long m) {
    cnt=2*n;
    ans=0;
    for(int i=1; i<=n; i++) pa[i]=n+i;
    for(int i=n+1; i<=cnt+m+1; i++) pa[i]=i;
    memset(b,0,sizeof(b));
}

删除就很简单了,直接将要删除的节点指向一个新的节点即可,它就可以被分离。

void dele(int x){
    pa[x]=cnt++;
}

cnt 为当前已用的点的数量。

代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath> 
using namespace std;
const int MN=10000005;
long long pa[MN],vis[MN],cnt,ans,n,m,x,y;
//pa[i]为自己的父亲节点。 
char ch;
void init(long long n, long long m) {
    cnt=2*n;
    ans=0;
    for(int i=0; i<n; i++) pa[i]=n+i;
    for(int i=n; i<=cnt+m; i++) pa[i]=i;
    memset(vis,0,sizeof(vis));
}
long long find(long long x){
    if(pa[x]==x) return x;
    return pa[x]=find(pa[x]);//路径压缩,pa直接赋值为代表元素。 
}
void merge(long long x, long long y) {
    pa[find(x)]=find(y);//让 x 的树根作为 y 的树根的子节点。 
}
void dele(int x){
    pa[x]=cnt++;
}
int main(){
	long long i=0;
    while(1){
    	scanf("%lld %lld",&n,&m);
        if(!n&&!m) break;//要是两个都是0,结束程序。 
        init(n,m);//预处理 
        i++;
        while(m--){
        	cin>>ch;
            if(ch=='M'){
            	scanf("%lld %lld",&x,&y);
                merge(x,y);//合并 
            }
            else{
            	scanf("%lld",&x);
            	dele(x);//删除这个点。 
			}
        }
        for(int k=0; k<n; k++){
            int tmp=find(k);
            if(!vis[tmp]) ans++,vis[tmp]=1;
        }
        printf("Case #%lld: %lld\n",i,ans);
    }
    return 0;
}

帮你AC彻底

如果你和曾经的我和我的好同学一样一直 UKE,但是又注册不了 SPOJ 的账号,那就请点这里按教程注册。

总结

  1. 编号从 0 开始到 n1,别错了。(曾经我可爱的同学就错了。)
  2. 要有 SPOJ 的账号。(来自 50 次 UKE 的倔强,和那之后的绝望。)
  3. 并查集的基础算法及其单点删除。

后记(并查集学习笔记)

定义(功能)

并查集是一种可以动态维护若干个不重叠的集合,并支持合并于查询的结构。

思维实现

可以想到,使用一颗树形结构来存储每个集合树根是集合的代表元素。

于是我们可以用一个 pa 数组来记录这个森林,faxx 的父节点,查询是只要递归求即可,两个集合合并时将 paroot1=root2

然而,上述做法时间复杂度堪忧,于是就需要路径压缩优化或者按秩合并。(这种这里不多讲,基本优化路径压缩就够了。)

其实很简单,只要在查询的时候八方问过的每个节点都直接指向树根即可,因为我们只关心每个集合对应的树形结构的根节点是什么,而不是它的形态。

时间复杂度 O(logn)

代码实现

  1. 初始化:

每个元素都独立形成一个集合,可以看成有若干个节点数为 1 的树,于是就将每个节点的父节点设为自己。

for(int i=1; i<=n; i++) pa[i]=i;
  1. 查询操作

x 是树根,则 x 就是集合代表,否则递归访问 pax 直至根节点。

long long find(long long x){
    if(pa[x]==x) return x;
    return pa[x]=find(pa[x]);//路径压缩,pa直接赋值为代表元素。 
}
  1. 合并操作

x 的树根作为 y 的树根的子节点。

void merge(long long x, long long y) {
    pa[find(x)]=find(y);//让 x 的树根作为 y 的树根的子节点。 
}

好拉,你应该已经学会了吧,那就往上看做这道题吧。

并查集题目推荐

  1. P3367 【模板】并查集(必会)
  2. [NOI2002] 银河英雄传说(带权并查集)
  3. [NOI2001] 食物链(带权并查集)
  4. UVA11987 Almost Union-Find(带权并查集的单点删除)
posted @   naroto2022  阅读(2)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示
花开如火,也如寂寞。