10年前的省选题,,果然是水题。
大意是说,有一个无向图,不一定联通,每次删掉一个点,给出当前联通块数。
第一反应暴力是去掉一个点就dfs一次,目测
但数据是200000,所以我们要优化一下。
联通块和并查集往往是同生共死的。
而给出的询问是一起给出来,并且前期的询问对后期不会有必须在线的影响。
这意味着我们可以先把所有要去掉的点去掉,从最后一个询问开始加回去,每加入一个点就把这个点能连出去的边走一遍,看看当前联通块个数。
人话说就是反向处理。
代码+注释
//本题编号0到n-1,所以我把所有输入都加了1,顺眼一些。
#include<bits/stdc++.h>
#define in read()
using namespace std;
int in{
int cnt=0,f=1;char ch=0;
while(!isdigit(ch)){
ch=getchar();
if(ch=='-')f=-1;
}
while(isdigit(ch)){
cnt=cnt*10+ch-48;
ch=getchar();
}
return cnt*f;
}
int n,m,flag[200003];
int k,tot;
int first[200003],nxt[1600003],to[1600003];
int que[200003];
int fa[200003];int vis[200003],ans[200003];
int all;
//n,m如题所示,flag记录这个点是否会被删除,k如题所示,tot等邻接表,que记录问题,fa并查集,vis深搜用,ans对应que。
//all没有作用。ovo
void add(int a,int b){
nxt[++tot]=first[a];
first[a]=tot;
to[tot]=b;
}
int find(int x){
if(fa[x]==x)return x;
return fa[x]=find(fa[x]);
}
int merge(int a,int b){
int x=find(a),y=find(b);
if(x!=y){
fa[x]=y;
return 1;
}
return 0;
}//邻接表+并查集操作
void dfs(int u,int faa){
for(int i=first[u];i;i=nxt[i]){
int v=to[i];
if(v==faa||flag[v]||vis[v])continue;
merge(u,v);
vis[v]=1;dfs(v,u);
}
}//只用一次的第一次dfs,将所有要删除的点去掉后形成的图建好
int dfs2(int u){
int bili=-1;int qwq=1;
for(int i=first[u];i;i=nxt[i]){
if(i==0)continue;
int v=to[i];
if(flag[v])continue;
if(merge(u,v)){
bili++;
}
}
return bili;
}//这个dfs是指将这次加入的这个点的出边走一遍,bili记录这个点能减少的联通块个数,因为本身这个点是一个联通块
//所以初始为-1。merge返回两个点是否在一个并查集里面。
int cnt;//最后阶段的剩余联通块数。
int main(){
freopen("starwar.in","r",stdin);
freopen("starwar.out","w",stdout);
n=in;m=in;int x,y;
for(int i=1;i<=n;i++)fa[i]=i;
for(int i=1;i<=m;i++){
x=in+1,y=in+1;
add(x,y);add(y,x);
}
k=in;
for(int i=1;i<=k;i++){
que[i]=in+1;flag[que[i]]=1;
}
for(int i=1;i<=n;i++){
if(!vis[i]&&!flag[i]){//没搜索过,且不是删除点
cnt++;
dfs(i,-1);
}
}ans[k]=cnt;//最后情况自然是cnt
for(int i=k;i>=1;i--){//反向处理
int x=que[i];flag[x]=0;//该点加入,flag为0。
cnt-=dfs2(x);//更新联通块数
ans[i-1]=cnt;//更新
}
for(int i=0;i<=k;i++)printf("%d\n",ans[i]);//要注意本题要求输出
//一开始的联通块个数,所以从0开始。
return 0;
}