P1197 [JSOI2008]星球大战 (并查集逆向拆分)

题目传送

我们可以运用逆向思维,将破坏转化为修复。利用并查集判断联通。

#include <iostream>
using namespace std;

const int maxn = 4e5 + 10;

struct node {
	int from, to, next;
}an[maxn]; //边

int n, m, k, cnt = 1;
int head[maxn]; //链头
int ans[maxn]; //输出答案
int broken[maxn]; 
int par[maxn];
int vis[maxn]; //是否被破坏

//链式前向星存图
void addedgs(int a, int b)
{
	an[cnt].from = a;
	an[cnt].to = b;
	an[cnt].next = head[a];
	head[a] = cnt;
	cnt++;
}

//找根
int find(int x)
{
	return par[x] == x ? x : par[x] = find(par[x]);
}

//合并
void unite(int a, int b)
{
	a = find(a);
	b = find(b);
	if (a == b) return;
	par[a] = b;
	return;
}

int main(void)
{
	ios::sync_with_stdio(false);
	cin >> n >> m;
	for (int i = 1; i <= n; i++) par[i] = i; //初始化并查集
	for (int i = 0; i < m; i++) {
		int a, b;
		cin >> a >> b;
		addedgs(a, b); //无向图
		addedgs(b, a);
	}
	cin >> k;
	int tot = n - k; //都没修复
	for (int i = 1; i <= k; i++) {
		cin >> broken[i];
		vis[broken[i]] = 1;
	}
	for (int i = 1; i <= 2 * m; i++) {
		//没被破坏且不同根,合并,联通量减1
		if (!vis[an[i].from] && !vis[an[i].to] && find(an[i].from) != find(an[i].to)) { 
			unite(an[i].from, an[i].to);
			tot--;
		}
	}
	ans[k + 1] = tot;
	//开始逆向修复
	for (int i = k; i >= 1; i--) {
		tot++; //被修复后,加1个单独的联通量
		vis[broken[i]] = 0; //被修复
		for (int j = head[broken[i]]; j; j = an[j].next) {
			if (!vis[an[j].to] && find(an[j].from) != find(an[j].to)) {
				unite(an[j].from, an[j].to);
				tot--;
			}
		}
		ans[i] = tot;
	}
	for (int i = 1; i <= k + 1; i++) cout << ans[i] << endl;
	return 0;
}
发布了30 篇原创文章 · 获赞 50 · 访问量 5302

猜你喜欢

转载自blog.csdn.net/qq_43054573/article/details/104270410