洛谷 P1197 [JSOI2008]星球大战

题目:洛谷江苏省选-星球大战

题意:

帝国军团要去打击我们的反抗军团,但是他们在打击的时候特别有素质,因为总是要按照顺序去打击(笑),但是反抗军团的高智商人群都死光了需要我们去统计在每次打击后现存的联通块数量,并输出

分析:

这道题,如果按死脑筋去做,想着都头皮发麻,毕竟要先创造并查集,然后又要去删除并查集,之前小编想按着这种方法去做,但貌似要用到维护森林之类的高端,彻底疯掉了。但这一题,我们可以使用逆向思维:拆掉(摧毁)→修复。这样我们就可以先构建一个图,然后将每次帝国军团要摧毁的点进行标记,最后用并查集去判断联通。(更详细看代码)

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define LL long long
using namespace std;
inline LL read() {
    LL d=0,f=1;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
    while(s>='0'&&s<='9'){d=d*10+s-'0';s=getchar();}
    return d*f;
}
int tot,f[400001],head[400001],b[400001],broken[400001],ans[400001];
struct w{
    int next,node,from;//from记录当前的父节点,node记录它的子节点,next记录上一个图
}h[400001];//h数组为图的集合
void jt(int x,int y)
{
    h[++tot].from=x;//将x记录为y的父节点
    h[tot].next=head[x];//将上一个图记录到next
    head[x]=tot;//标上一个图为tot
    h[tot].node=y;//将x的子节点记录为y
    return;
}
int find(int i)//找最终的祖先
{
    return f[i]==i? i:f[i]=find(f[i]);//加速递归
}
void hb(int x,int y)//合并两个集合
{
    int a,b;
    a=find(f[x]);b=find(f[y]);
    f[a]=b;
}
int main()
{
    int n,m;
    n=read();m=read();
    int a,c;
    for(int i=0;i<=n;i++) 
      f[i]=i,head[i]=-1;//并查集初始化
    for(int i=1;i<=m;i++)
    {
        a=read();c=read();
        jt(a,c);//建图,因为是双向图,故建图两次
        jt(c,a);
    }
    int k;
    k=read();
    for(int i=1;i<=k;i++)
    {
        b[i]=read();
        broken[b[i]]=1;//标记已经被毁坏
    }
    int d=n-k;//开始时,当做每个点都是独立的,d为统计联通块数
    for(int i=1;i<=m*2;i++)
        if(!broken[h[i].from]&&!broken[h[i].node]&&find(h[i].from)!=find(h[i].node))//当这两个点都没有被毁坏,且原本不属于同一集合,则可以合并
          {
            hb(h[i].from,h[i].node);
            d--;//连一条边,联通块-1
          }
    ans[k+1]=d;//当前的d,就是最后一次遭到帝国军团的攻击的结果
    for(int i=k;i>0;i--)
    {
        d++;//修复一个点,联通块数+1。但d不用清0,因为我们要在废墟的基础上重新修复
        broken[b[i]]=0;//将修复的点标记回0
        for(int j=head[b[i]];j!=-1;j=h[j].next)//枚举每一个子节点
        {
            if(!broken[h[j].node]&&find(b[i])!=find(h[j].node))//同上
            {
                d--;
                hb(h[j].node,b[i]);
            }
        }
        ans[i]=d;//此时的d,就是第i次破坏前的结果
    }
    for(int i=1;i<=k+1;i++) printf("%d\n",ans[i]);//输出
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值