洛谷[1197][JSOI2008]星球大战

@星球大战

题目大意

  现有N个星球,这些星球通过M条隧道直接或间接相连。帝国将要进行K次操作,每次会消灭掉N个星球中的一个,而此星球被消灭后与它相连的隧道也随之消失。求帝国每次操作后存留的星球连通块数量。

输入输出

  输入的第一行为被空格隔开的N与M
  接下来每行两个被空格隔开的数X与Y,表示X与Y间有隧道相连
  接下来一行有一个整数K
  然后是K行,每行一个整数表示帝国要消灭的星球的编号

算法讨论

  当我们刚看到这题,想着怎么在摧毁一个星球时计算连通块的时候可能会受阻手足无措。但如果用逆向思维倒着来想,那就会变得很简单,并且很快就能想到使用并查集。
  我们只需要在读入星球间的隧道和K个摧毁操作时先存起来,用不摧毁的星球和它们的隧道维护一个并查集。接着倒着将K个要摧毁的星球合并到原来的并查集中,在合并的同时记录连通块的变化,同时将每个状态的连通块数量纪录起来,最后倒着输出。

数据结构

  多次提到“倒着”,当然是可以使用栈来存放数据。

代码实现

#include <cstdio>
#include <iostream>
#include <vector>
using namespace std;
int N, M, F[400005]={0}, K;
vector<int> E[400005];
int Kill[400005], pt=-1; //消灭栈
int Result[400005], rpt=-1; //结果栈
bool is_kill[400005]={false}; //记录是否被杀
//其实我应该用killed......

int Find(int x){
    if (F[x]!=x) F[x]=Find(F[x]);
    return F[x];
} //并查集-查找祖先

void Merge(int a, int b){
    if (Find(a)==Find(b)) return;
    K--; //合并成功的话连通块就少了一个
    F[Find(b)] = Find(a);
} //并查集-合并集合

int main(){

    int a, b, nowc=0;
    scanf("%d %d", &N, &M);
    for (int i=0; i<N; i++) F[i]=i; //初始化
    for(int i=0; i<M; i++){
        scanf("%d %d", &a, &b);
        E[a].push_back(b); //双向边
        E[b].push_back(a); //推两次
    }
    scanf("%d", &K);

    while(K--){
        scanf("%d", &Kill[++pt]); //入栈
        is_kill[Kill[pt]] = true; //标记
    }
    K = N-pt-1; //再次利用废弃的变量K来记录连通块数量数量
    //一开始的连通块数量就是星球的数量
    for(int i=0; i<N; i++){
        if (is_kill[i]) continue;
        for(int j=0; j<E[i].size(); j++){
            if (is_kill[E[i][j]]) continue;
            Merge(i, E[i][j]); //逆推,将此点合并回去
        }
    }
    Result[++rpt] = K;
    while(pt>=0){
        K++; //多一个星球,多一个连通块
        is_kill[Kill[pt]] = false; //去除标记
        for(int i=0; i<E[Kill[pt]].size(); i++){
            if (is_kill[E[Kill[pt]][i]]) continue;
            Merge(Kill[pt], E[Kill[pt]][i]);
        }
        pt--; //指向下一个
        Result[++rpt] = K; //将结果入栈
    }

    while (rpt>=0) printf("%d\n", Result[rpt--]);
    //出栈,输出

    return 0;
}
posted @ 2017-10-08 11:02  HelloElwin  阅读(208)  评论(0)    收藏  举报