hdu 2473 Junk-Mail Filter【并查集好题、建立虚拟节点】

本文讨论了使用并查集数据结构解决垃圾邮件识别问题。通过执行一系列操作,如将疑似相同类型的垃圾邮件合并,以及将已被误识别的邮件隔离,作者展示了如何有效地跟踪并分类垃圾邮件的独特特性。

Junk-Mail Filter

Time Limit: 15000/8000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 8640    Accepted Submission(s): 2735

Problem Description

Recognizing junk mails is a tough task. The method used here consists of two steps:
1) Extract the common characteristics from the incoming email.
2) Use a filter matching the set of common characteristics extracted to determine whether the email is a spam.

We want to extract the set of common characteristics from the N sample junk emails available at the moment, and thus having a handy data-analyzing tool would be helpful. The tool should support the following kinds of operations:

a) “M X Y”, meaning that we think that the characteristics of spam X and Y are the same. Note that the relationship defined here is transitive, so
relationships (other than the one between X and Y) need to be created if they are not present at the moment.

b) “S X”, meaning that we think spam X had been misidentified. Your tool should remove all relationships that spam X has when this command is received; after that, spam X will become an isolated node in the relationship graph.

Initially no relationships exist between any pair of the junk emails, so the number of distinct characteristics at that time is N.
Please help us keep track of any necessary information to solve our problem.

 

 

Input

There are multiple test cases in the input file.
Each test case starts with two integers, N and M (1 ≤ N ≤ 105 , 1 ≤ M ≤ 106), the number of email samples and the number of operations. M lines follow, each line is one of the two formats described above.
Two successive test cases are separated by a blank line. A case with N = 0 and M = 0 indicates the end of the input file, and should not be processed by your program.

 

 

Output

For each test case, please print a single integer, the number of distinct common characteristics, to the console. Follow the format as indicated in the sample below.

 

 

Sample Input

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

 

 

Sample Output

Case #1: 3

Case #2: 2

 

 

Source

2008 Asia Regional Hangzhou

 

题目大意:有n个信封,m个操作,一共有两种操作:

1、M a,b表示a号信封和b号信封属于同一种信封。

2、S a 表示把a号信封孤立出来属于一种信封。

 

思路:对于M a,b操作,直接判定它们为同一种信封merge起来就好。我们还是详细来说明一下S操作,对于孤立出来一个信封,让它独立成为一种类的信封,最直接能够想到的方法就是对其重新编号,例如有这样的操作:

4 4

M 0 1

M 0 2

M 0 3

S 0

这个时候我们直接更改0号为4号的话,我们就能使得达到目的。

 

 如果变成了这样,我们就达到了目标,那么我们如何使得0号节点被4号节点所代替呢?不难想到,我们可以让0号节点虚拟出来一个4号父节点,那是不是直接f【0】=4就行了呢?明显是不可以的,因为根据并查集的特点,这个时候我们find(1)find(2)find(3)都等于4,显然4号变成了所有节点的父节点,并没有孤立成功。

 那么如何解决这个问题呢?我们让0、1、2、3号节点都虚拟出来一个父节点:


 这个时候我们再连接:

M 0 1 

M 0 2

M 0 3

就有:


显然0、1、2、3这个时候父节点都变成了4,也就是说这四个信封属于一种,这个时候我们:

S 0

孤立出0号信封的时候,我们对0号重新虚拟一个父节点,内容就变成了:

这样,我们就有两种信封了,一种是8,一种是4,很巧妙的解决了这个问题,而且接下来如果有其他操作,他们节点之间也不会有影响。


 AC代码:

 

#include<stdio.h>
#include<string.h>
using namespace std;
int vis[1200000];
int f[1200000];
int find(int a)
{
    int r=a;
    while(f[r]!=r)
    r=f[r];
    int i=a;
    int j;
    while(i!=r)
    {
        j=f[i];
        f[i]=r;
        i=j;
    }
    return r;
}
void merge(int a,int b)
{
    int A,B;
    A=find(a);
    B=find(b);
    if(A!=B)
    f[B]=A;
}
int main()
{
    int n,m;
    int kase=0;
    while(~scanf("%d%d",&n,&m))
    {
        if(n==0&&m==0)break;
        int cont=n*2;
        for(int i=0;i<n;i++)
        {
            f[i]=i+n;
        }
        for(int i=n;i<2*n+m+1;i++)
        {
            f[i]=i;
        }
        for(int i=0;i<m;i++)
        {
            char op[5];
            scanf("%s",op);
            if(op[0]=='M')
            {
                int x,y;
                scanf("%d%d",&x,&y);
                merge(x,y);
            }
            if(op[0]=='S')
            {
                int x;
                scanf("%d",&x);
                f[x]=cont++;
            }
        }
        int output=0;
        memset(vis,0,sizeof(vis));
        for(int i=0;i<n;i++)
        {
            int tmp=find(i);
            if(vis[tmp]==0)
            {
                vis[tmp]=1;
                output++;
            }
        }
        printf("Case #%d: %d\n",++kase,output);
    }
}














评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值