FOJ-2155 盟国

http://acm.fzu.edu.cn/problem.php?pid=2155
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

思路

并查集的删除。
某国退盟,但要保证该国的所有的子关系结点仍然在联盟中,因此需要增加一个数组id来辅助保存一个节点是否被删除,被删除则值为delcnt,未被删除则值为正常的节点编号。如果在退盟后又结盟,原先的delcnt会变成正常节点编号。所以需要在定义数组时做一定的扩大。
也就是说,在id[x]里把退盟国的父亲设置为一个越界的值delcnt,而查找时在未删除结点的p数组中进行,此时在p数组中依然会经过退盟国原先的节点,这样就能让退盟国的子节点可以经由该国找到原先的根节点,而退盟国自身查找到的是越界的值p[id[x]] = delcnt(>N),即自身为根节点。

代码

#include <cstdio>
#include <cstring>
const int MAX_N = 100000 * 10 + 10;
int N, M, cnt, p[MAX_N], r[MAX_N], id[MAX_N];
bool vis[MAX_N];
void init()
{
    memset(vis, false, sizeof vis);
    for (int i = 0; i < MAX_N; i++)
        p[i] = id[i] = i, r[i] = 0;
}
int find(int x)
{
    return p[x] == x ? x : p[x] = find(p[x]);
}
void unite(int x, int y)
{
    x = find(id[x]), y = find(id[y]);
    if (x == y)
        return;
    if (r[x] < r[y])
        p[x] = y;
    else
    {
        p[y] = x;
        if (r[x] == r[y])
            r[x]++;
    }
}
void del(int x, int& delcnt)
{
    id[x] = delcnt++;
}
int main()
{
    while (~scanf("%d%d", &N, &M), N)
    {
        init();
        int ans = 0, delcnt = N;
        for (int i = 0, a, b; i < M; i++)
        {
            getchar();
            char c = getchar();
            if (c == 'M')
                scanf("%d%d", &a, &b), unite(a, b);
            else
                scanf("%d", &a), del(a, delcnt);
        }
        for (int i = 0, t; i < N; i++)
            t = find(id[i]), vis[t] ? true : ans++, vis[t] = true;
        printf("Case #%d: %d\n", ++cnt, ans);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值