8-13 日 (AtCoder Beginner Contest 264)T5 Blackout 2 题解

题面

题面大意:

         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+m2105

         1 ≤ Q ≤ E ≤ 5 ∗ 1 0 5 1 \leq Q \leq E \leq 5 * 10^5 1QE5105


分析:

         因为 删过的边不会再加回来,所以我们要想出一种 在线 的操作处理。但是一条边连接的 两个联通块 我们并不能快速其 内部的信息。因此正着考虑十分困难。

         秉着 正难则反 的原则。我们可以将问题转化一下:我们来思考 如何将 删边 转化为 加边。对于最开始给定的边,我们先不将它们都连上,只连那些在后面询问中从未被删除的边,然后我们 倒着处理 询问,对于当前询问,我们先统计出当前图中有多少个 有电气城市然后再将这次询问要删去的边加上 即可。这样就将问题转化成了 加边。 因为 只要与发电站联通就算 有电气城市,所以在同一个连通块中的点的状态都是一样的。 我们可以考虑 使用一个点来记录这个连通块的信息。这就让我们想到了 并查集

        我们用 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(Qlogn+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;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值