洛谷 1197 [JSOI2008]星球大战 并查集

题目:
https://www.luogu.org/problem/show?pid=1197

明确:无向图求解联通块问题常常用并查集;

所以我们第一反应是并查集
但并查集只能“添加”,不能删除;

注意到原题有:帝国总是按输入的顺序依次摧毁星球的;

所以我们可以考虑着用并查集,这样就可以只”添加”,而不用删除,在并查集的适用范围中;

思路:
将摧毁顺序反过来;

然后不断向集合里加点

每有一个摧毁的点,联通块个数加1;
如果它可以与某一集合合并,那么联通块个数减一(两个本不联通的块合并);

最后,我们倒序输出;

考虑到,每有一个摧毁的点,我们需要遍历它的出边;
由于是无向图,实际最多每条边要遍历两次;

所以总复杂度为O(mlogn);

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;

const int MAXN=2000001;
int fa[MAXN],sc[MAXN],ans[MAXN],fst[MAXN<<1],nxt[MAXN<<1];
int n,m,q,cnt,tot,num;
struct hh
{
    int from,to;
}ma[MAXN],ss[MAXN];
bool vis[MAXN];

int find(int x)
{
    int r=x,t;
    while(r!=fa[r]) r=fa[r];
    while(x!=r) t=fa[x],fa[x]=r,x=t;
    return r;
}

void build(int f,int t)
{
    num++;
    ss[num]=(hh){f,t};
    nxt[num]=fst[f];
    fst[f]=num;
    return;
}

void solve()
{
    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",&ma[i].from,&ma[i].to);
        build(ma[i].from,ma[i].to);
        build(ma[i].to,ma[i].from);
        int fx=find(ma[i].from),fy=find(ma[i].to);
        if(fx!=fy)
            fa[fx]=fy;
    }
    scanf("%d",&q);

    for(int i=1;i<=q;i++) scanf("%d",&sc[i]),vis[sc[i]]=1;

    for(int i=0;i<n;i++) fa[i]=i;//初始化; 

    for(int j=1;j<=m;j++)
    {
        int x=ma[j].from,y=ma[j].to;
        if(vis[x] || vis[y]) continue;
        else
        {
            int fx=find(x),fy=find(y);
            if(fx!=fy)
                fa[fx]=fy;
        }
    }

    cnt=0;
    for(int i=0;i<n;i++) if(!vis[i] && fa[i]==i) cnt++;

    num=ans[++tot]=cnt;

    for(int i=q;i>=1;i--)
    {
        int x=sc[i];
//      cout<<x<<" *)*)*)"<<endl;
        num++;
        vis[sc[i]]=0;
        for(int j=fst[x];j;j=nxt[j])
        {
            int y=ss[j].to;
            if(vis[y]) continue;
            int fx=find(x),fy=find(y);
            if(fx!=fy) 
            num--,fa[fx]=fy;
        //  cout<<num<<"*****"<<endl;
        }
        ans[++tot]=num;
//      cout<<num<<"*(*(*("<<endl;
    }

    for(int i=tot;i>=1;i--) printf("%d\n",ans[i]);
}

int main()
{
    solve();
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值