习题——星球大战
星球大战
大意:给一个n个点的图,有m条双向边,k条指令,每条指令去掉一个点和与它相连的所有边,要求在线回答每次操作后的连通块个数。
emm...第一次做的时候直接暴力了上去,居然有30分...
第二次再看想到要用并查集,但是好难搞啊。
第三次...终于看了题解,恍然大悟。
谨此纪念。
在线判断连通性,时间复杂度必须很优秀。
学过并查集之后,容易看出这题需要用并查集来解决。
尝试用并查集去做,然而,在正向的推导过程中,我们就已经发现了从正面入手的难度。
不如,采用逆向思维?
先记录指令,标记被摧毁了的点。
求出所有的指令都执行完了之后的情况,再一步一步向上倒推。
对于每一个被摧毁了的点,连通块个数 ++。
再枚举与它相连的点:
判断两点是否已经连通,只需要判断它们是否在同一个并查集里。
如果不连通,直接合并,并将连通块个数 -- 。
似乎就可以愉快地 AC 了呢。
#include<bits/stdc++.h> const int M=200005; const int N=M<<1; using namespace std; int n,m,k,x,y,tot,cnt,d[N],fa[N],ans[M],head[N<<1],from[N<<1],var[N<<1],nex[N<<1]; bool b[N]; inline void add(int u,int v){ var[++cnt]=v,from[cnt]=u;nex[cnt]=head[u],head[u]=cnt; } inline int Get(int x){return fa[x]==x?x:fa[x]=Get(fa[x]);} inline void Merge(int x,int y){fa[Get(x)]=Get(y);} int main(){ scanf("%d%d",&n,&m); for(int i=0;i<n;++i) fa[i]=i,head[i]=-1; for(int i=1;i<=m;++i){ scanf("%d%d",&x,&y); add(x,y),add(y,x); } scanf("%d",&k); tot=n-k; for(int i=1;i<=k;++i) scanf("%d",&d[i]),b[d[i]]=1; for(int i=1;i<=m;++i){ int u=from[2*i],v=var[2*i]; if(!b[u] && !b[v] && Get(u)!=Get(v)) tot--,Merge(u,v); } ans[k+1]=tot; for(int i=k;i>=1;--i){ tot++;b[d[i]]=0; for(int j=head[d[i]];j!=-1;j=nex[j]) if(!b[var[j]] && Get(var[j])!=Get(d[i])) tot--,Merge(var[j],d[i]); ans[i]=tot; } for(int i=1;i<=k+1;++i) printf("%d\n",ans[i]); return 0; }