现在的位置: 首页 > 综合 > 正文

HDU 2473 Junk-Mail Filter(带分离操作的并查集)

2017年12月17日 ⁄ 综合 ⁄ 共 1472字 ⁄ 字号 评论关闭

题目链接:Click here~~

题意:

给 n 个独立的节点,定义一种具有传递性的关系,两种操作:

1、合并,即合并 u 和 v 各自所在的集合。

2、分离,将 u 从之前的集合中分离出来,并将其作为一个新的独立节点。

最后找出 n 个节点一共存在于多少个集合中。

解题思路:

从合并操作来看,很容易想到并查集,但是我们在学习并查集时,知道传统的并查集是不支持分离操作的。如何完成这一操作呢?

想办法调整策略,使得分离操作不悖于之前的合并操作,并且对之后的合并操作也不产生影响。

先回顾一下问题,然后我们考虑将一个已经存在于一个集合的节点 u 分离出来,会对并查集产生什么影响呢?

如果什么都不处理,无脑分离,直接将 u 的根指向自己的话,那些将根指向 u 的节点没有得到更新,而且我们也没有什么好的方法去快速更新它们。

网上的题解大都分为两种吧。

先讲解下方法一,也是我采用的方法:分离时,舍弃掉那些被分离的点,直接为并查集引入一个新的节点。

“舍弃” 的意思,是不再处理那个被分离的点与其他节点的关系。

举个例子,假如一共有 4 个节点 V = {1,2,3,4},分为两个集合 S1 = {1,2,3},S2={4}。

现在将节点 3 分离,我们保留这个节点 3,不去管它,而是引入一个新的节点 5 代替节点 3,此时,S1 = {1,2,3},S2 = {4},S3 = {5}。

之后所有关于节点 3 的操作全部去对于节点 5 进行。如果节点 5 又被删除的话,那么同样的方法再继续引入新的节点即可。

方法二:为每个节点引入一个虚拟点,作为它的父亲,由并查集合并操作的特点,它将一直是树中的叶子节点,而对于所有集合的根节点一直是虚拟点。分离时,直接将这个节点指向另一个新的独立的虚拟点即可。

#include <stdio.h>
#include <string.h>
#include <algorithm>

using namespace std;

const int N = 1e6 + 1e5 + 5;

namespace ufSet
{
    int pre[N],map[N],top;
    void init(int n){
        memset(pre,-1,sizeof(pre));
        for(int i=0;i<n;i++)
            map[i] = i;
        top = n;
    }
    int root(int u){
        return pre[u] == -1 ? u : pre[u] = root(pre[u]);
    }
    bool gather(int u,int v){
        int r1 = root(u);
        int r2 = root(v);
        if(r1 == r2)
            return false;
        else
            pre[r1] = r2;
            return true;
    }
    void apart(int u){
        map[u] = top++;
    }
}using namespace ufSet;

bool vis[N];

int main()
{
    int n,q,ncase = 0;
    while(~scanf("%d%d",&n,&q),n||q)
    {
        memset(vis,false,sizeof(vis));
        init(n);
        while(q--)
        {
            char op[4];
            int u,v;
            scanf("%s",op);
            if(op[0] == 'M')
            {
                scanf("%d%d",&u,&v);
                gather(map[u],map[v]);
            }
            else
            {
                scanf("%d",&u);
                apart(u);
            }
        }
        for(int i=0;i<n;i++)
            vis[root(map[i])] = true;
        int ans = 0;
        for(int i=0;i<top;i++)
            if(vis[i])
                ++ans;
        printf("Case #%d: %d\n",++ncase,ans);
    }
    return 0;
}

抱歉!评论已关闭.