题目链接
https://www.luogu.com.cn/problem/P1197
解题思路
1.这题最容易想到的方法就是用并查集搜索;在每次摧毁一个节点之后,把与该节点相连的边全部擦去,重新扫描图;
但是这个方法太费时,肯定不行;
2.`逆向思维`
我们不妨在一开始建图的时候就不考虑会被摧毁的点,直接建最后的图;
然后把每一次的摧毁操作当成修复操作;再利用并查集判断联通即可;
代码展示
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=4e5+10;
/*
1.存储所有边
2.存储所有被摧毁的点
3.遍历所有点,找出存在的边
4.依次恢复点,每次增加这个点能构成的所有边
*/
struct edge{
int from,to;
};
//存储边
vector<int>G[maxn];
//存储图
edge Edge[maxn];
bool ds[maxn];
int K[maxn];
int father[maxn];
int ans[maxn];
int Find(int x){
if(x==father[x])
return x;
father[x]=Find(father[x]);
return father[x];
}
int main(){
int n,m;
cin>>n>>m;
int f=-1;
for(int i=0;i<n;i++)father[i]=i;
for(int i=0;i<m;i++){
int a,b;
cin>>a>>b;
G[a].push_back(b);
G[b].push_back(a);
f++;
Edge[f].from=a,Edge[f].to=b;
f++;
Edge[f].from=b,Edge[f].to=a;
}
//初始化
int k;
cin>>k;
for(int i=0;i<k;i++){
cin>>K[i];
ds[K[i]]=true;//被摧毁
}
int total=n-k;//值得注意的是,一开始的节点个数应该按照n-k处理
for(int i=0;i<f;i++){
if(ds[Edge[i].from]==false&&ds[Edge[i].to]==false){
//两个点都没被炸毁
int ff=Find(Edge[i].from);
int ft=Find(Edge[i].to);
if(ff!=ft){
father[ff]=ft;
total--;
}
}
}
ans[k]=total;
for(int i=k-1;i>=0;i--){
ds[from]=false;
total++;//修复一个点,把这个点先当成独立的连通块
int from=K[i];
for(int j=0;j<G[from].size();j++){
int to=G[from][j];
if(ds[to]==false){
//一条边的两个点都没被炸毁
int ff=Find(from);
int ft=Find(to);
if(ff!=ft){
father[ff]=ft;
total--;
}
}
}
ans[i]=total;
}
for(int i=0;i<=k;i++)
cout<<ans[i]<<endl;
return 0;
}
总结
1.这题的难度并不大,只要我们能转过来
想一想;
2.另外就是在创建标记符的时候,像flag,一定要记住true与false分别代表什么意思;
拓展练习
https://www.luogu.com.cn/problem/P1653