并查集应用: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;
}

相关文章

显卡天梯图2024最新版,显卡是电脑进行图形处理的重要设备,...
初始化电脑时出现问题怎么办,可以使用win系统的安装介质,连...
todesk远程开机怎么设置,两台电脑要在同一局域网内,然后需...
油猴谷歌插件怎么安装,可以通过谷歌应用商店进行安装,需要...
虚拟内存这个名词想必很多人都听说过,我们在使用电脑的时候...