P1197 [JSOI2008]星球大战

算法

并查集+逆序

思路

做这道题前呢,我们先出门左转关闭农场,一道类似的更简单一丢丢的题

然后,我们考虑一下这题,因为并没有过多的操作,只是要我们求一下连通块的个数而已(也就是连通性,具有传递性的连通),而这恰好是并查集所擅长的。

然而,我们正向看题目时就会发现不支持删除操作的并查集似乎办不到。但,如果我们把删除操作换成加入操作,就可以办到了。这也就是逆向思维。题目支持离线,所以我们可以逆向处理出所有答案以后再全部输出就好了。

考虑最后的连通状态:标记摧毁的点,把不摧毁的点之间能连的边连起来,同时统计连通块的数量即为所求

中间的连通状态:于最后的连通状态处理方法类似,从后往前依次加入被删除的点,枚举出边,连通。统计连通块的数量即为所求

倒序存,正序输shu 

#include<bits/stdc++.h>
using namespace std;
int now[400010],tot;
int team[400010];
bool flag[400010];//是否存在
int father[400010];
int n,m,k;
int ans[400010];
int sum;
struct node
{
    int from;
    int to;
    int next;
}a[400010];//邻接表存图
void put(int x,int y)
{
    a[tot].from=x;
    a[tot].next=now[x];
    a[tot].to=y;
    now[x]=tot;
    tot++;
}
int find(int x)
{
    if(x!=father[x])father[x]=find(father[x]);
    return father[x];
}//并查集基本操作
void unionn(int x,int y)
{
    x=find(x),y=find(y);
    if(x==y)return;
    sum--;//合并集合减一
    father[x]=y;
}//同上
int main()
{
    scanf("%d%d",&n,&m);
    int x,y;
    for(int i=0;i<n;i++)
        father[i]=i,now[i]=-1;//初始化
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&x,&y);
        put(x,y);
        put(y,x);//存图
    }
    scanf("%d",&k);
   
    sum=n-k;//假设剩下的都不联通
    for(int i=1;i<=k;i++)
    {
        scanf("%d",&x);
        flag[x]=true;
        team[i]=x;
    }
   
    for(int i=0;i<2*m;i++)
    {
        if(!flag[a[i].from]&&!flag[a[i].to])
        {
            unionn(a[i].from,a[i].to);//链接所有边
        }
    }
    //printf("k:%d
",k);
    ans[k+1]=sum;
    for(int i=k;i>=1;i--)
    {
        int u=team[i];
        sum++;//新增一个点+1连通块
        flag[u]=false;
        for(int i=now[u];i!=-1;i=a[i].next)
        {
            if(!flag[a[i].from]&&!flag[a[i].to])//都在并查集内(没被摧毁)
            {
                unionn(a[i].from,a[i].to);
            }
        }
        ans[i]=sum;
    }//倒着做
  // printf("k:%d
",k);
    for(int i=1;i<=k+1;i++)
    printf("%d
",ans[i]);

}

  

原文地址:https://www.cnblogs.com/ruanmowen/p/12731110.html