习题——星球大战

星球大战

大意:给一个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;
}

猜你喜欢

转载自www.cnblogs.com/zmjqwq/p/10806601.html