Fzu 2155 盟国【并查集的增删】裸题

 Problem 2155 盟国

Accept: 229    Submit: 804
Time Limit: 5000 mSec    Memory Limit : 32768 KB

 Problem Description

世界上存在着N个国家,简单起见,编号从0~N-1,假如a国和b国是盟国,b国和c国是盟国,那么a国和c国也是盟国。另外每个国家都有权宣布退盟(注意,退盟后还可以再结盟)。

定义下面两个操作:

“M X Y” :X国和Y国结盟

“S X” :X国宣布退盟

 Input

多组case。

每组case输入一个N和M (1 ≤ N ≤ 100000 , 1 ≤ M ≤ 1000000),N是国家数,M是操作数。

接下来输入M行操作

当N=0,M=0时,结束输入

 Output

对每组case输出最终有多少个联盟,格式见样例。

 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

思路:


如果在学完并查集并且专心刷过专题的同学们对这种题肯定是不陌生的。


我们如果直接暴力将宣布退出联盟的国家拆除的话,会改变原来树的形状,使得原本属于同一集合的点,find(i)不相等。

所以我们只要能够保留原来树的形状即可。

那么对于退出联盟的国家,我们新建立一个点,作为这个点的当前节点。我们不再去使用之前的这个国家所用的编号,而是用当前新建立出来这个点作为之后操作当前国家使用的编号。

我们维护一个数组now【i】表示当前节点i所代表的编号。

那么这样就鞥能够保留原来树的形状并且不会影响之后的操作了。


另外注意一点,在查询结果的时候,我们要注意因为有点可能原来是一个祖先节点,所以我们并不能统计f【now【i】】==now【i】(find(now【i】)==now【i】)的点的数量。而是我们要统计祖先的种类数。


Ac代码:

#include<stdio.h>
#include<string.h>
#include<map>
using namespace std;
int f[1200500];
int now[1200500];
int vis[1200500];
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;
}
int merge(int a,int b)
{
    int A,B;
    A=find(a);
    B=find(b);
    if(A!=B)
    {
        f[B]=A;
        return 1;
    }
    return 0;
}
int main()
{
    int n,m;
    int kase=0;
    while(~scanf("%d%d",&n,&m))
    {
        if(n==0&&m==0)break;
        int pos=n;
        for(int i=0;i<n;i++)f[i]=i,now[i]=i;
        while(m--)
        {
            char str[15];
            scanf("%s",str);
            if(str[0]=='M')
            {
                int x,y;
                scanf("%d%d",&x,&y);
                merge(now[x],now[y]);
            }
            else
            {
                int x;
                scanf("%d",&x);
                now[x]=pos;
                f[pos]=pos;
                pos++;
            }
        }
        int output=0;
        map<int ,int >s;
        memset(vis,0,sizeof(vis));
        for(int i=0;i<n;i++)
        {
            vis[find(now[i])]++;
            if(vis[find(now[i])]==1)output++;
        }
        printf("Case #%d: %d\n",++kase,output);
    }
}






评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值