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);
}
}