并查集应用:Blackout 2

原题链接

题目描述

题目大意是你有 n 个城市,m 个发电站,现在有 E 条边。

连接两个地点(任意两个地点都可以,无论它们是城市还是发电站),现在你有 q 次操作,每次操作你可以删掉 E 条边中的一条。

现在问你每次操作之后,需要统计一下可以通过这些边到达某个发电站的城市个数,注意每次操作是永久的,前面的操作会一直延续到结束

输入样例
5 5 10
2 3
4 10
5 10
6 9
2 9
4 8
1 7
3 6
8 10
1 8
6
3
5
8
10
2
7
输出样例
4
4
2
2
2
1

算法

(并查集 + 逆向操作 + 超级源点)

本题要求 q 次询问,每次询问都要查询有多少城市通电,那么相当于从 m 个发电站开始走,看能走到多少个城市,之后对其取并集。但这样太过麻烦,我们可以考虑将 m 个发电站压缩成 1 个超级源点(即为点 0),将这个超级源点与城市连接边。

于是,我们不难想到在每次询问时,每次删边后都以超级源点作为起点对整个图进行遍历,但这样的做法太暴力了,时间复杂度达到了 O ( q n ) O(qn) O(qn)

所以我们可以想到一种取巧的方法,由于删边之后的查找难度要大于加边,我们可以考虑先把删边后的最终的状态的点用 并查集 维护起来,然后再逆向加回要删的边。同时用一个map来维护每个连通块有多少个城市被联通,每次加回边的时候查询 超级源点(0) 所在的 map值 作为答案,然后利用并查集观察边的两点是否是同一个连通块,不是的话就加入并查集,并将两者的 map值 累加 ( 注意尽量将map值累加到超级源点 ) 。

时间复杂度 O ( q l o g n ) O(qlogn) O(qlogn)

#include <iostream>
#include <cstring>
#include <algorithm>
#include <set>
#include <vector>
#include <map>
using namespace std;
typedef pair<int,int> PII;
const int N = 1e6 + 10,INF = 1e9;
int p[N];
set<int> s;
PII w[N];
vector<int> res,qid;

int seek(int x)
{
    if (p[x] != x) p[x] = seek(p[x]);
    return p[x];
}
int main()
{
    int n,m,e;
    scanf("%d%d%d",&n,&m,&e);
    map<int,int> mp;//维护每个连通块有多少个城市被联通
    for (int i = 1;i <= n;i ++ ) p[i] = i,mp[i] = 1;
    for (int i = 1;i <= e;i ++ ) {
        int u,v;
        scanf("%d%d",&u,&v);
        u = u > n ? 0 : u,v = v > n ? 0 : v;
        w[i] = {u,v};
    }

    int q;
    scanf("%d",&q); 
    while (q -- ) {
        int x;
        scanf("%d",&x);
        s.insert(x),qid.push_back(x);
    }
    for (int i = 1;i <= e;i ++ ) {
        if (!s.count(i)) {
            int u = seek(w[i].first),v = seek(w[i].second);
            if (u != v) {
                if (u == 0) swap(u,v);//这里是为了把 mp 都累加到超级源点上
                p[u] = v;
                mp[v] += mp[u]; //注意累加u所附带的所有边
            }
        } 
    }

    for (int i = qid.size() - 1;i >= 0;i -- ) {
        //for (int i = 1;i <= n;i ++ ) cout << p[i] << ' ';puts("");
        res.push_back(mp[0]);
        int u = w[qid[i]].first,v = w[qid[i]].second;

        u = seek(u),v = seek(v);
        if (u != v) {
            if (u == 0) swap(u,v);//这里是为了把 mp 都累加到超级源点上
            p[u] = v;
            mp[v] += mp[u]; 
        }
    }
    for (int i = res.size() - 1;i >= 0;i --) printf("%d\n",res[i]);
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

marvel121

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值