JSOI2008——星球大战

题目:https://www.luogu.org/problemnew/show/1197

并查集。

难点是若依次去掉点在求连通块个数,时间太长。

精妙的思维:先全部读入,再逆向求连通块个数——增加点比删去点对于求个数更容易!

小技巧:求个数时可以先设个数s为n,每次合并一个fa[ ]就s - -;但此题中别忘了增加点时要s++,且一开始s不是n而是n-w。

#include<iostream>
#include<cstdio>
using namespace std;
int n,m,x,y,xnt,nex[400005],fa[400005],w,de[400005],cnt,s,c[400005];
bool bb[400005];
struct Node{
    int next,to;
}edge[400005];
void add(int a,int b)
{
    xnt++;
    edge[xnt].next=nex[a];
    edge[xnt].to=b;
    nex[a]=xnt;
}
int find(int a)
{
    if(fa[a]==a)return a;
    return fa[a]=find(fa[a]);
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=0;i<n;i++)
        fa[i]=i;
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&x,&y);
        add(x,y);add(y,x);
    }
    scanf("%d",&w);
    s=n-w;
    for(int i=1;i<=w;i++)
    {
        scanf("%d",&x);
        de[++cnt]=x;
        bb[x]=1;
    }
    for(int i=0;i<n;i++)
        if(!bb[i])
        {
            int u=find(i);
            for(int j=nex[i];j;j=edge[j].next)
            {
                int k=edge[j].to;
                if(bb[k])continue;
                int v=find(k);
                if(u!=v)
                {
                    fa[v]=u;
                    s--;
                }
            }
        }
    c[cnt+1]=s;
    for(int i=cnt;i;i--)
    {
        s++;
        int k=de[i];
        bb[k]=0;
        int u=find(k);
        for(int j=nex[k];j;j=edge[j].next)
        {
            int r=edge[j].to;
            if(bb[r])continue;
            int v=find(r);
            if(u!=v)
            {
                fa[v]=u;
                s--;
            }
        }
        c[i]=s;
    }
    for(int i=1;i<=w+1;i++)
        printf("%d
",c[i]);
    return 0;
}
原文地址:https://www.cnblogs.com/Narh/p/8276718.html