luogu P1197 [JSOI2008]星球大战-并查集题解

先上题目

题目描述

很久以前,在一个遥远的星系,一个黑暗的帝国靠着它的超级武器统治着整个星系。 某一天,凭着一个偶然的机遇,一支反抗军摧毁了帝国的超级武器,并攻下了星系中几乎所有的星球。这些星球通过特殊的以太隧道互相直接或间接地连接。
但好景不长,很快帝国又重新造出了他的超级武器。凭借这超级武器的力量,帝国开始有计划地摧毁反抗军占领的星球。由于星球的不断被摧毁,两个星球之间的通讯通道也开始不可靠起来。
现在,反抗军首领交给你一个任务:给出原来两个星球之间的以太隧道连通情况以及帝国打击的星球顺序,以尽量快的速度求出每一次打击之后反抗军占据的星球的连通块的个数。(如果两个星球可以通过现存的以太通道直接或间接地连通,则这两个星球在同一个连通块中)。

输入格式

输入文件第一行包含两个整数,n,m,分别表示星球的数目和以太隧道的数目。星球用 0∼n−1 的整数编号。 接下来的 m 行,每行包括两个整数 x,y,表示星球 x 和星球 y 之间有 “以太” 隧道,可以直接通讯。 接下来的一行为一个整数 k
,表示将遭受攻击的星球的数目。 接下来的 k 行,每行有一个整数,按照顺序列出了帝国军的攻击目标。这 k 个数互不相同,且都在 0 到
n-1 的范围内。

输出格式

第一行是开始时星球的连通块个数。接下来的 kk 行,每行一个整数,表示经过该次打击后现存星球的连通块个数。

数据范围

对于 100% 的数据,1 ≤ m ≤ 2×10^5,1 ≤ n ≤ 2m,x ≠ y

题目链接

https://www.luogu.com.cn/problem/P1197

此题若是用模拟、暴力的路子去写,实现难度不高,但是因为数据范围的问题一定会超时。所以不妨用逆向思维,在得到攻击的星球顺序后倒着写,用 “攻下” 星球去代替 “摧毁” 星球。
这样的话,就不需要反复的遍历来得到每一步的答案了。

明白了解题关键,代码实现其实只需要并查集和一点点的预处理了。
下面上代码

#include<bits/stdc++.h>
using namespace std;
int cnt=1,fa[400005],vis[400005],d[400005],ans[400005],head[400005];
struct Edge {
    int u,v,next;
}edge[400005];
void add(int u,int v) { //链式星向前存图
    edge[cnt].v=v;
    edge[cnt].next=head[u];
    head[u]=cnt++;
}
int find(int x) {
    if(x==fa[x]) return x;
    else return fa[x]=find(fa[x]);
}
int main() {
    int n,m,k;
    cin>>n>>m;
    for(int i=0;i<n;i++) fa[i]=i; //对父节点初始化,自身的父节点就是自己
    for(int i=0;i<n;i++) vis[i]=1; //vis数组来代表i星球目前是否还存在,一开始先初始化全都存在未被摧毁
    for(int i=0,u,v;i<m;i++) {
        cin>>u>>v;
        add(u,v);
        add(v,u);
    }
    cin>>k;
    for(int i=0;i<k;i++) {
        cin>>d[i];
        vis[d[i]]=0; //将要摧毁的星球全都标记为已不存在
    }
    int total=n-k; //total代表的是当前的连通块,此时所有要摧毁的星球已被摧毁,且未进行连通,所以将total初始化为星球总数减去摧毁的星球数
    for(int i=0;i<n;i++) { //遍历各个星球
        if(vis[i]) {
            for(int j=head[i];j;j=edge[j].next) { //遍历这个星球的连通关系
                int v=edge[j].v;
                if(vis[v] && find(i)!=find(v)) { //若两个星球都未被摧毁且不再一个连通块,则合并
                    fa[find(v)]=find(i); 
                    total--; //两个连通块合并了,所以当前连通块数减一
                }
            }
        }
    }
    for(int i=k-1;i>=0;i--) { //从后向前遍历要摧毁的星球
        ans[i]=total; //当前要摧毁的星球摧毁后的连通块数
        //每次遍历完成后的total其实是下一个星球摧毁后的连通数,所以要在遍历的最前面将答案存入ans数组
        int u=d[i];
        vis[u]=1; 
        total++; //此时要将这个要摧毁的星球恢复原状,所以要把vis归1,total连通块数加一
        for(int j=head[u];j;j=edge[j].next) {
            int v=edge[j].v;
            if(vis[v] && find(u)!=find(v)) {
                fa[find(v)]=find(u);
                total--;
            }
        }
    }
    cout<<total<<endl; //这时候的total就等于没有摧毁星球时的连通块数
    for(int i=0;i<k;i++)
        cout<<ans[i]<<endl;
    return 0;
}

第一次写博客,可能又啰嗦又没讲到点上,如果有不懂的可以留言。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值