洛谷 1197 & bzoj 1015 [JSOI2008]星球大战 题解(并查集,思维)

原题链接:
洛谷
bzoj

题意简述

给定一个无向图,和 k k k个删除操作。第一行输出最开始有多少个联通块,接下来 k k k行输出删除每个点后,剩下的点组成多少个联通块。

样例

(输入输出格式懒得给了,以后也不想给了。。。大致明白题意即可。输入输出处理就是细节部分了。)
输入
8 13
0 1
1 6
6 5
5 0
0 6
1 2
2 3
3 4
4 5
7 1
7 2
7 6
3 6
5
1
6
3
5
7
输出
1
1
1
2
3
3

思路

我们知道并查集是珂以维护连边的,很好维护。

但是我们如何处理删边呢?

可持久化

显然不好写,也很明显不是最优解。我们考虑离线。经过思考后会发现,删除是加边的逆运算,我们只要“时间倒流”,先求删完之后的联通块个数,然后一个一个把边加回去,从后往前求。这样就只涉及加边的操作,就不用维护删边了。

#include<bits/stdc++.h>
using namespace std;
namespace Flandle_Scarlet
{
    #define N 400100
    #define p_b push_back
    vector<int> to[N];//维护最开始的图
    int n,m,k;
    int fk[N],ans[N];
    bool vis[N];
    void R1(int &x)
    {
        x=0;char c=getchar();int f=1;
        while(c<'0' or c>'9') f=(c=='-')?-1:1,c=getchar();
        while(c>='0' and c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
        x=(f==1)?x:-x;
    }
    void Input()
    {
        R1(n),R1(m);
        for(int i=1;i<=m;++i)
        {
            int a,b;
            R1(a),R1(b);
            ++a,++b;//我是习惯从1开始编号的。。。
            to[a].p_b(b);
            to[b].p_b(a);
        }
        R1(k);
        for(int i=1;i<=k;++i)
        {
            R1(fk[i]);
            ++fk[i];
            vis[fk[i]]=1;
        }
    }

    class DSU//并查集
    {
        public:
            int Father[N],Cnt[N];
            void Init()
            {
                for(int i=0;i<N;i++)
                {
                    Father[i]=i;
                    Cnt[i]=1;
                }
            }
            int Find(int x)
            {
                return (x==Father[x])?x:(Father[x]=Find(Father[x]));
            }
            bool Merge(int x,int y)
            {
                int ax=Find(x),ay=Find(y);
                if (ax==ay) return 0;
                if (Cnt[ax]<Cnt[ay])
                {
                    Cnt[ay]+=Cnt[ax];
                    Father[ax]=ay;
                }
                else
                {
                    Cnt[ax]+=Cnt[ay];
                    Father[ay]=ax;
                }
                return 1;
            }
    }D;
    void Soviet()
    {
        D.Init();//别忘了初始化
        int cnt=n;//联通块个数
        for(int u=1;u<=n;++u)
        {
            if (!vis[u])
            {
                for(int j=0;j<to[u].size();++j)
                {
                    int v=to[u][j];
                    if (!vis[v])
                    {
                        cnt-=D.Merge(u,v);
                    }
                }
            }
        }//处理最后一个答案
        ans[k+1]=cnt-k;//注意要减去k,因为被删除的点是不会被计算到联通块里的
        for(int i=k;i>=1;--i)
        {
            int u=fk[i];
            vis[u]=0;
            for(int j=0;j<to[u].size();++j)
            {
                int v=to[u][j];
                if (!vis[v])
                {
                    cnt-=D.Merge(u,v);//重新连上u,v的边
                }
            }
            ans[i]=cnt-(i-1);//同理要减去(i-1)
        }
        for(int i=1;i<=k+1;++i)
        {
            printf("%d\n",ans[i]);//输出答案
        }putchar('\n');
    }
    void IsMyWife()
    {
        if (1)
        {
//            freopen("","r",stdin);
//            freopen("ans.txt","w",stdout);
        }
        Input();
        Soviet();
    }
};
int main()
{
    Flandle_Scarlet::IsMyWife();
    return 0;
}

回到总题解界面

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值