洛谷[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;
}

浙公网安备 33010602011771号