题面
题面大意:
有 n + m n + m n+m 个 点, E E E 条双向边。其中前 n n n 个点为城市点,后 m m m 个点为发电站。若一个城市与至少一个发电站联通,则称这个城市为 有电气城市,现有 Q Q Q 次询问,每次询问给出一条边的编号,问 当前图 删除这条边 后还有几座 有电气城市,每次询问后删除的边不会再被加上 (永久删除)。
其中 n + m ≤ 2 ∗ 1 0 5 n + m \leq 2 * 10^5 n+m≤2∗105
1 ≤ Q ≤ E ≤ 5 ∗ 1 0 5 1 \leq Q \leq E \leq 5 * 10^5 1≤Q≤E≤5∗105
分析:
因为 删过的边不会再加回来,所以我们要想出一种 在线 的操作处理。但是一条边连接的 两个联通块 我们并不能快速其 内部的信息。因此正着考虑十分困难。
秉着 正难则反 的原则。我们可以将问题转化一下:我们来思考 如何将 删边 转化为 加边。对于最开始给定的边,我们先不将它们都连上,只连那些在后面询问中从未被删除的边,然后我们 倒着处理 询问,对于当前询问,我们先统计出当前图中有多少个 有电气城市,然后再将这次询问要删去的边加上 即可。这样就将问题转化成了 加边。 因为 只要与发电站联通就算 有电气城市,所以在同一个连通块中的点的状态都是一样的。 我们可以考虑 使用一个点来记录这个连通块的信息。这就让我们想到了 并查集。
我们用 c n t [ f a ] cnt[fa]% cnt[fa] 来记录当前以 节点 f a fa fa 为父亲的连通块的 大小(城市节点数量)。用 f [ f a ] f[fa] f[fa] 表示这个连通块的状态。具体来讲,若 f [ f a ] = 1 f[fa] = 1 f[fa]=1 ,则这个连通块与发电厂相连,否则就没有发电厂与之相连。 最后我们使用这些信息进行传递即可。
时间复杂度: O ( Q ∗ l o g n + m ) O(Q * log_{n + m}) O(Q∗logn+m)
CODE:
#include<bits/stdc++.h>//倒着想
using namespace std;
const int N = 2e5 + 10;
const int M = 5e5 + 10;
int n, m, e_num, q, u[M], v[M], del[M], bin[N], cnt[N], ans[M];//cnt[i] 表示以i为连通块的根的联通块的大小
bool flag[M], f[N];
struct edge{
int v, last;
}E[M * 2];
int Find(int x){
if(x == bin[x]) return x;
else return bin[x] = Find(bin[x]);
}
int main(){
scanf("%d%d%d", &n, &m, &e_num);
for(int i = 1; i <= e_num; i++){
scanf("%d%d", &u[i], &v[i]);
}
scanf("%d", &q);
for(int i = 1; i <= q; i++){
scanf("%d", &del[i]);
flag[del[i]] = 1;
}
for(int i = 1; i <= n + m; i++){
bin[i] = i;
if(i <= n) cnt[i] = 1;
}
for(int i = n + 1; i <= n + m; i++){//标记电厂
f[i] = 1;
}
for(int i = 1; i <= e_num; i++){
if(flag[i]) continue;
if(Find(u[i]) != Find(v[i])){
int f1 = Find(u[i]), f2 = Find(v[i]);
cnt[f1] += cnt[f2];
bin[f2] = f1;
if(f[f1] | f[f2]){//合并
f[f1] = 1;
}
}
}
int res = 0;
for(int i = 1; i <= n + m; i++){//初始答案
if(bin[i] == i){
if(f[i] == 1) res += cnt[i];
}
}
for(int i = q; i >= 1; i--){
ans[i] = res;//更新 必须放在前面
int k = del[i];
int f1 = Find(u[k]), f2 = Find(v[k]);//找到两个点的父亲节点
if(f1 != f2){
if(f[f1] == 1 && f[f2] == 0) res += cnt[f2];
else if(f[f1] == 0 && f[f2] == 1) res += cnt[f1];
bin[f2] = f1;
cnt[f1] += cnt[f2];
if(f[f1] || f[f2]) f[f1] = 1;
}
}
for(int i = 1; i <= q; i++){
printf("%d\n", ans[i]);
}
return 0;
}