P1197 [JSOI2008] 星球大战

# P1197 [JSOI2008] 星球大战

## 题目描述

很久以前,在一个遥远的星系,一个黑暗的帝国靠着它的超级武器统治着整个星系。

某一天,凭着一个偶然的机遇,一支反抗军摧毁了帝国的超级武器,并攻下了星系中几乎所有的星球。这些星球通过特殊的以太隧道互相直接或间接地连接。

但好景不长,很快帝国又重新造出了他的超级武器。凭借这超级武器的力量,帝国开始有计划地摧毁反抗军占领的星球。由于星球的不断被摧毁,两个星球之间的通讯通道也开始不可靠起来。

现在,反抗军首领交给你一个任务:给出原来两个星球之间的以太隧道连通情况以及帝国打击的星球顺序,以尽量快的速度求出每一次打击之后反抗军占据的星球的连通块的个数。(如果两个星球可以通过现存的以太通道直接或间接地连通,则这两个星球在同一个连通块中)。

## 输入格式

输入文件第一行包含两个整数,$n,m$,分别表示星球的数目和以太隧道的数目。星球用 $0 \sim n-1$ 的整数编号。

接下来的 $m$ 行,每行包括两个整数 $x,y$,表示星球 $x$ 和星球 $y$ 之间有 “以太” 隧道,可以直接通讯。

接下来的一行为一个整数 $k$ ,表示将遭受攻击的星球的数目。

接下来的 $k$ 行,每行有一个整数,按照顺序列出了帝国军的攻击目标。这 $k$ 个数互不相同,且都在 $0$ 到 $n-1$ 的范围内。

## 输出格式

第一行是开始时星球的连通块个数。接下来的 $k$ 行,每行一个整数,表示经过该次打击后现存星球的连通块个数。

## 输入输出样例 #1

### 输入 #1

```
8 13
0 1
1 6
6 5
5 0
0 6
1 2
2 3
3 4
4 5
7 1
7 2
7 6
3 6
5
1
6
3
5
7
```

### 输出 #1

```
1
1
1
2
3
3
```

## 说明/提示

【数据范围】  
对于 $100\%$ 的数据,$1\le m \le 2\times 10^5$,$1\le n \le 2m$,$x \neq y$。

正解

如果正向思维的话,是让我们去点,这让我们很头大,但如果反向思维,那么就是添加点求连通块,这不就是并查集吗,那么下面说一下反向思维

我们可以先把所有要攻击的点去掉,然后把剩余的点根据边来连在一起,那么就是所有要攻击的点攻击完后,剩下的连通块,这是我们在从最后一个攻击的点来添加,每添加一个个点,我们就用并查集来更新连通块的数量

#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;

const int N = 1e6 + 10;

struct node {
    int x, y;
} road[N];

int n, m, cnt, k; // cnt表示连通块的数量
int h[N], e[N], ne[N], idx;
int fa[N];
bool st[N]; // 判断星球是否被摧毁
int attack_order[N]; // 存储攻击顺序
int ans[N]; // 存储每次操作后的连通块数量

int find(int x)
{
    if (fa[x] != x) fa[x] = find(fa[x]);
    return fa[x];
}

void add(int a, int b) {
    e[idx] = b;
    ne[idx] = h[a];
    h[a] = idx++;
}

int main()
{
    cin >> n >> m;

    memset(h, -1, sizeof h);
    for (int i = 0; i < m; i++) {
        cin >> road[i].x >> road[i].y;
        add(road[i].x, road[i].y);
        add(road[i].y, road[i].x); // 正确添加双向边
    }

    cin >> k;
    for (int i = 0; i < k; i++) {
        int x;
        cin >> x;
        st[x] = true; // 标记星球为被摧毁
        attack_order[i] = x; // 存储攻击顺序
    }

    // 初始化并查集
    for (int i = 0; i < n; i++) fa[i] = i;

    // 计算初始连通块数量(所有未被摧毁的星球)
    cnt = n - k;

    // 处理未被摧毁的星球之间的边
    for (int i = 0; i < m; i++) {
        int x = road[i].x, y = road[i].y;
        if (st[x] || st[y]) continue; // 跳过被摧毁的星球
        int xx = find(x), yy = find(y);
        if (xx == yy) continue;
        fa[xx] = yy;
        cnt--; // 合并连通块
    }

    ans[k] = cnt; // 存储所有攻击完成后的连通块数量

    // 逆向恢复被攻击的星球
    for (int i = k - 1; i >= 0; i--) {
        int j = attack_order[i]; // 获取当前恢复的星球
        st[j] = false; // 恢复星球
        cnt++; // 新增一个连通块

        // 检查与当前恢复星球相连的所有边
        for (int t = h[j]; t != -1; t = ne[t]) { // 修正:使用t = ne[t]
            int u = e[t];
            if (st[u]) continue; // 跳过仍被摧毁的星球

            int xx = find(j), yy = find(u);
            if (xx != yy) {
                fa[xx] = yy; // 合并连通块
                cnt--;
            }
        }

        ans[i] = cnt; // 存储当前步骤的连通块数量
    }

    // 输出结果
    for (int i = 0; i <= k; i++) {
        cout << ans[i] << endl;
    }

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值