原题链接
题目描述
题目大意是你有 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;
}