其他分享
首页 > 其他分享> > 「图论」第1章 并查集课堂过关

「图论」第1章 并查集课堂过关

作者:互联网

文章目录


「图论」第1章 并查集课堂过关

A. 【例题1】【模板】并查集

题目

代码

#include <iostream>
#include <cstdio>
#define nn 10010
using namespace std;
int read() {
	int re = 0;
	char c = getchar();
	while(c < '0' || c > '9')
		c = getchar();
	while(c >= '0' && c <= '9')
		re = (re << 1) + (re << 3) + c - '0',
		c = getchar();
	return re;
}

int n , m;
int fa[nn];

int findroot(int x) {
	return fa[x] == x ? fa[x] : (fa[x] = findroot(fa[x]));
}
void uni(int x , int y) {
	if(findroot(x) != findroot(y))
		fa[findroot(x)] = findroot(y);
}
int main() {
	n = read() , m = read();
	for(int i = 1 ; i <= n ; i++)
		fa[i] = i;
	for(int i = 1 ; i <= m ; i++) {
		int ty , x , y;
		ty = read() , x = read() , y = read();
		if(ty == 1)
			uni(x , y);
		else
			puts(findroot(x) == findroot(y) ? "Y" : "N");
	}
	return 0;
}

B. 【例题2】程序自动分析

题目

代码

#include <iostream>
#include <cstdio>
#include <cstring>
#define nn 1000010
using namespace std;
int read() {
	int re = 0;
	char c = getchar();
	while(c < '0' || c > '9')
		c = getchar();
	while(c >= '0' && c <= '9')
		re = (re << 1) + (re << 3) + c - '0',
		c = getchar();
	return re;
}
int head[nn] , nxt[nn] , dat[nn];
const int mod = 100007;
int find(int x) {
	static int cnt = 0;
	for(int i = head[x % mod] ; i ; i = nxt[i]) {
		if(dat[i] == x)
			return i;
	}
	++cnt;
	nxt[cnt] = head[x % mod] , dat[cnt] = x , head[x % mod] = cnt;
	return cnt;
}

int fa[nn];

int findroot(int x) {
	return fa[x] == x ? fa[x] : (fa[x] = findroot(fa[x]));
}
void uni(int x , int y) {
	if(findroot(x) != findroot(y))
		fa[findroot(x)] = findroot(y);
}
int n , m;
int neq[nn][2];
int main() {
	
	int T = read();
	while(T--) {
		memset(head , 0 , sizeof(head));
		memset(nxt , 0 , sizeof(nxt));
		memset(dat , 0 , sizeof(dat));
		
		m = 0;
		n = read();
		for(int i = 1 ; i <= nn - 1 ; i++)
			fa[i] = i;
		for(int i = 1 ; i <= n ; i++) {
			int x = find(read()) , y = find(read());
			if(read() == 1)
				uni(x , y);
			else {
				++m;
				neq[m][0] = x;
				neq[m][1] = y;
			}
		}
		bool res = true;
		for(int i = 1 ; i <= m ; i++) {
			if(findroot(neq[i][0]) == findroot(neq[i][1])) {
				res = false;
				break;
			}
		}
		puts(res ? "YES" : "NO");
		
	}
	return 0;
}

C. 【例题3】银河英雄传说

题目

题目背景

公元 5801年,地球居民迁至金牛座 α \alpha α 第二行星,在那里发表银河联邦创立宣言,同年改元为宇宙历元年,并开始向银河系深处拓展。

宇宙历 799年,银河系的两大军事集团在巴米利恩星域爆发战争。泰山压顶集团派宇宙舰队司令莱因哈特率领十万余艘战舰出征,气吞山河集团点名将杨威利组织麾下三万艘战舰迎敌。

题目描述

杨威利擅长排兵布阵,巧妙运用各种战术屡次以少胜多,难免恣生骄气。在这次决战中,他将巴米利恩星域战场划分成 30000 30000 30000 列,每列依次编号为 1 , 2 , … , 30000 1,2,…,30000 1,2,…,30000。之后,他把自己的战舰也依次编号为 1 , 2 , … , 30000 1,2,…,30000 1,2,…,30000,让第 i i i号战舰处于第 i i i 列,形成“一字长蛇阵”,诱敌深入。这是初始阵形。当进犯之敌到达时,杨威利会多次发布合并指令,将大部分战舰集中在某几列上,实施密集攻击。合并指令为 M i j,含义为第 i i i号战舰所在的整个战舰队列,作为一个整体(头在前尾在后)接至第 j j j号战舰所在的战舰队列的尾部。显然战舰队列是由处于同一列的一个或多个战舰组成的。合并指令的执行结果会使队列增大。

然而,老谋深算的莱因哈特早已在战略上取得了主动。在交战中,他可以通过庞大的情报网络随时监听杨威利的舰队调动指令。

在杨威利发布指令调动舰队的同时,莱因哈特为了及时了解当前杨威利的战舰分布情况,也会发出一些询问指令:C i j。该指令意思是,询问电脑,杨威利的第 i i i号战舰与第 j j j号战舰当前是否在同一列中,如果在同一列中,那么它们之间布置有多少战舰。

作为一个资深的高级程序设计员,你被要求编写程序分析杨威利的指令,以及回答莱因哈特的询问。

输入格式

第一行有一个整数 T T T( 1 ≤ T ≤ 5 × 1 0 5 1 \le T \le 5 \times 10^5 1≤T≤5×105),表示总共有 T T T条指令。

以下有 T T T行,每行有一条指令。指令有两种格式:

  1. M i j: i i i 和 j j j 是两个整数( 1 ≤ i , j ≤ 30000 1≤i,j≤30000 1≤i,j≤30000),表示指令涉及的战舰编号。该指令是莱因哈特窃听到的杨威利发布的舰队调动指令,并且保证第 i i i 号战舰与第 j j j 号战舰不在同一列。
  2. C i j: i i i 和 j j j 是两个整数( 1 ≤ i , j ≤ 30000 1≤i,j≤30000 1≤i,j≤30000),表示指令涉及的战舰编号。该指令是莱因哈特发布的询问指令。

输出格式

依次对输入的每一条指令进行分析和处理:

  • 如果是杨威利发布的舰队调动指令,则表示舰队排列发生了变化,你的程序要注意到这一点,但是不要输出任何信息。
  • 如果是莱因哈特发布的询问指令,你的程序要输出一行,仅包含一个整数,表示在同一列上,第 i i i 号战舰与第 j j j 号战舰之间布置的战舰数目。如果第 i i i 号战舰与第 j j j 号战舰当前不在同一列上,则输出 -1。

输入输出样例

输入 #1

4
M 2 3
C 1 2
M 2 4
C 4 2

输出 #1

-1
1

说明/提示

战舰位置图:表格中阿拉伯数字表示战舰编号

img

思路

先吐槽一句:我写正解一下就写出来了,一个暴力居然要写那么久

最原始思路

直接用链+并查集,并查集判断两战舰是否在同一队列中,每次询问就顺着链往下摸就可以了,时间复杂度 O ( n 2 ) O(n^2) O(n2)好像也能过

一次优化

记录 f r o n t i front_i fronti​,满足战舰 f r o n t i front_i fronti​在 i i i的前面,记录 v a l i val_i vali​表示 f r o n t i front_i fronti​和 i i i(不含 i i i)之间有多少战舰, s i z i siz_i sizi​表示以 i i i为首的舰队的数量(若 i i i 不在队首,则 s i z i siz_i sizi​无意义),每次合并维护一下,询问时一级一级跳就好了,最坏复杂度 O ( n 2 ) O(n^2) O(n2)

二次优化

我们定期维护 f r o n t i front_i fronti​和 v a l i val_i vali​,即如果当前链过长,我们直接让 f r o n t i front_i fronti​指向当前舰队的第一个战舰,同事更新 v a l val val.

那么我们何时维护呢,根据分块/莫队的思想(没学没关系),若链的长度 l e n i len_i leni​超过 n \sqrt n n ​,我们就对所有战舰进行 f r o n t front front和 v a l val val的维护.这样,链的长度基本不超过 n \sqrt n n ​,询问次数为 n n n,则询问的时间为 O ( n n ) O(n\sqrt n) O(nn ​);维护一次的复杂度为 O ( n ) O(n) O(n)(每个点最多遍历两次,常数忽略),最多进行 n \sqrt n n ​次维护,维护的复杂度为 O ( n n ) O(n\sqrt n) O(nn ​)

综上,时间复杂度为 O ( n n ) O(n\sqrt n) O(nn ​)

最终优化

复杂度接近 O ( n ) O(n) O(n),可以冲到洛谷最优解的第一页

根据上面的思路,我们为什么不把 v a l val val的更新融入到并查集的路径压缩中呢?即路径压缩的同时完成更新

更具体地,讲结合代码分析

代码

二次优化

#include <iostream>
#include <cstdio>
#define nn 300010
//#pragma GCC optimize(2)
using namespace std;
int read() {
	int re = 0;
	char c = getchar();
	while(c < '0' || c > '9')
		c = getchar();
	while(c >= '0' && c <= '9')
		re = (re << 1) + (re << 3) + c - '0',
		c = getchar();
	return re;
}

int head[nn] , end[nn];
int nxt[nn] , id[nn];
int front[nn];
void insert(int x , int y) {
	static int cnt = 0;
	++cnt;
	if(head[x] == 0)	head[x] = end[x] = cnt;
	else 				nxt[end[x]] = head[y];
	id[cnt] = y , end[x] = end[y];
}

int n = 30000;
int fa[nn];
int siz[nn];
int val[nn];
int len[nn];

int findroot(int x) {
	return fa[x] == x ? fa[x] : (fa[x] = findroot(fa[x]));
}
void uni(int x , int y) {
	if(findroot(x) != findroot(y))
		fa[findroot(x)] = findroot(y);
}
int cnt1 ,  cnt2;
int getans(int x) {
	int sum = 0;
	int cnt = 0;
	do {
		sum += val[x];
		x = front[x];
		++cnt;
		++cnt1;
	}while(front[x] != x);
	return sum;
}
int abs_(int x) {return x < 0 ? -x : x;}
int main() {
	for(int i = 1 ; i <= n ; i++)
		fa[i] = i , siz[i] = 1 , val[i] = 0 , front[i] = i , len[i] = 1 , insert(i , i);
	
	int T = read();
	while(T--) {
		char ty = getchar();
		while(ty != 'M' && ty != 'C')	ty = getchar();
		int u = read() , v = read();
		if(ty == 'M') {
			u = findroot(u) , v = findroot(v);
			insert(v , u);
			front[u] = v;
			fa[u] = v;
			val[u] = siz[v] , siz[v] += siz[u] , len[v] += len[u];
			if(len[v] * len[v] >= n) {
				for(int j = 1 ; j <= n ; j++)
					if(fa[j] == j) {
						len[j] = 1;
						int v = 0;
						for(int i = head[j] ; i ; i = nxt[i])
							val[id[i]] = v++ , front[id[i]] = j , ++cnt2;
					}
			}
		}
		else {
			if(findroot(u) != findroot(v))
				puts("-1");
			else {
				printf("%d\n" , abs_(getans(u) - getans(v)) - 1);
			}
		}
	}
	return 0;
}

最终优化

#include <iostream>
#include <cstdio>
#define nn 300010
using namespace std;
int read() {
	int re = 0;
	char c = getchar();
	while(c < '0' || c > '9')
		c = getchar();
	while(c >= '0' && c <= '9')
		re = (re << 1) + (re << 3) + c - '0',
		c = getchar();
	return re;
}

int n = 30000;
int val[nn];//i到fa[i]节点有多少个战舰(含i)
int siz[nn];
int prefaval[nn];//pre_FatherValue	之前的/父亲节点的权值,注意不是:之前的父亲节点/的权值
int fa[nn];

int findroot(int x) {
	if(fa[x] == x)	return x;
	int prefa = fa[x];
	fa[x] = findroot(fa[x]);//路径压缩
	val[x] += val[prefa] - prefaval[x];//应该能理解吧,val[prefa]是更新后的值,而fa[x]变了,x和prefa之间的战舰数量是不变的
	prefaval[x] = val[fa[x]];
	return fa[x];
}

inline int abs_(int x) {return x < 0 ? -x : x;}
int main() {
	for(int i = 1 ; i <= n ; i++)
		fa[i] = i , val[i] = 1 , siz[i] = 1;//注意初始化
	int T = read();
	for(int i = 1 ; i <= T ; i++) {
		char ty = getchar();
		while(ty != 'M' && ty != 'C')	ty = getchar();
		int u = read() , v = read();
		if(ty == 'M') {
			u = findroot(u);
			v = findroot(v);
			fa[u] = v;
			val[u] += siz[v];
			siz[v] += siz[u];
			prefaval[u] = val[fa[u]];
		}
		else {
			if(findroot(u) != findroot(v))//检查是否在同一舰队,顺便更新val
				puts("-1");
			else
				printf("%d\n" , abs_(val[u] - val[v]) - 1);
		}
	}
	return 0;
}

D. 【例题4】食物链

题目

题目描述

动物王国中有三类动物 A,B,C,这三类动物的食物链构成了有趣的环形。A 吃 B,B 吃 C,C 吃 A。

现有 N 个动物,以 1 - N 编号。每个动物都是 A,B,C 中的一种,但是我们并不知道它到底是哪一种。

有人用两种说法对这 N 个动物所构成的食物链关系进行描述:

  • 第一种说法是 1 X Y,表示 X 和 Y 是同类。
  • 第二种说法是2 X Y,表示 X 吃 Y 。

此人对 N 个动物,用上述两种说法,一句接一句地说出 K 句话,这 K 句话有的是真的,有的是假的。当一句话满足下列三条之一时,这句话就是假话,否则就是真话。

  • 当前的话与前面的某些真的话冲突,就是假话
  • 当前的话中 X 或 Y 比 N 大,就是假话
  • 当前的话表示 X 吃 X,就是假话

你的任务是根据给定的 N 和 K 句话,输出假话的总数。

输入格式

第一行两个整数,N,K,表示有 N 个动物,K 句话。

第二行开始每行一句话(按照题目要求,见样例)

输出格式

一行,一个整数,表示假话的总数。

输入输出样例

输入 #1

100 7
1 101 1
2 1 2
2 2 3
2 3 3
1 1 3
2 3 1
1 5 5

输出 #1

3

说明/提示

1 ≤ N ≤ 5 ∗ 10^4

1 ≤ K ≤ 10^5

关于种类并查集(务必先看)

先放一道题(种类并查集的模板题,建议先做了,也不用多少时间,否则根本理解不了「食物链」):

模板题

思路

很简单,喜欢贪心的贪心,喜欢二分答案的二分答案,问题是我们如何判断两个罪犯在/不在同一个监狱内

普通并查集可以解决朋友关系,但对于敌人关系,就显得力不从心了:“敌人的敌人是我的朋友”,这个如何处理呢?

因此,种类并查集登场:

我觉得这张图可以很好描述种类并查集:

下面讲故事:

世界是由两部分组成的我们称为一号世界和二号世界,万物都有其对立面,为了保持平衡,若 i i i在一号世界是阳极,那么 i i i在二号世界就是阴极,反之,若 i i i在一号世界是阴极,那么 i i i在二号世界就是阳极.更进一步地说,若 i i i和 j j j在同一个世界中是相反的,那么一号世界的 i i i和二号世界的 j j j就是同类的,二号世界的 i i i和一号世界的 j j j也是同类的.

这么说够形象了吧

一,二号世界就是两个并查集,这样就能判断 i i i和 j j j的敌对关系了.
我们把并查集拓展到 2 n 2n 2n,下标1$n$就是"一号世界",$n$+1 2 n 2n 2n就是"二号世界"

完整代码

#include <iostream>
#include <cstdio>
using namespace std;
#define N 20010
#define M 100010
int read() {
	int re = 0;
	char c = getchar();
	while(c < '0' || c > '9')
		c = getchar();
	while(c >= '0' && c <= '9')
		re = (re << 1) + (re << 3) + c - '0',
		c = getchar();
	return re;
}
int n , m;
int fa[N * 2];

int x[M] , y[M] , c[M] , maxc;
int findroot(int x) {
	return fa[x] == x ? x : (fa[x] = findroot(fa[x]));
}
void uni(int x , int y) {
	if(findroot(x) != findroot(y))
		fa[findroot(x)] = findroot(y);
}
bool check(int C) {
	for(int i = 1 ; i <= n ; i++)
		fa[i] = i , fa[i + n] = i + n;
	for(int i = 1 ; i <= m ; i++) {
		if(c[i] > C) {
			if(findroot(x[i]) == findroot(y[i]) || findroot(x[i] + n) == findroot(y[i] + n))//若确定x[i],y[i]在同一监狱
				return false;
			uni(x[i] + n , y[i]);//确定x[i],y[i]不在同一监狱
			uni(x[i] , y[i] + n);
		}
	}
	return true;
}
int main() {
	n = read() , m = read();
	for(int i = 1 ; i <= m ; i++)
		x[i] = read() , y[i] = read() , maxc = ((c[i] = read()) > maxc ? c[i] : maxc) ;
		
	int l = 0 , r = maxc;
	while(l < r) {
		int mid = (l + r) / 2;
		if(check(mid))
			r = mid;
		else
			l = mid + 1;
	}
	cout << l;
	return 0;
}

本题思路

跟上面那题差不多,把并查集扩大到 3 n 3n 3n,对于一个 x x x, x x x吃 x + n x+n x+n, x + n x+n x+n吃 x + 2 n x+2n x+2n, x + 2 n x+2n x+2n吃 x x x

对于同类的情况,直接合并:

			uni(x , y);
			uni(x + n , y + n);
			uni(x + 2 * n , y + 2 * n);

对于 x x x吃 y y y的情况:

有以下关系 a → b a\to b a→b表 a a a吃 b b b :
{ x → y x → x + n x + n → x + 2 n x + 2 n → x y → y + n y + n → y + 2 n y + 2 n → y \begin{cases} x&\to y\\ x&\to x+n\\ x+n&\to x+2n\\ x+2n&\to x\\ y&\to y+n\\ y+n&\to y+2n\\ y+2n&\to y \end{cases} ⎩⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎧​xxx+nx+2nyy+ny+2n​→y→x+n→x+2n→x→y+n→y+2n→y​
可得到以下同类关系:

			uni(x , y + 2 * n);
			uni(x + 2 * n , y + n);
			uni(y , x + n);

对于判断冲突,自己理解下,参考下代码就出来啦

代码

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
#define N 50010
#define LIE {++ans;continue;}
int read() {
	int re = 0;
	char c = getchar();
	while(c < '0' || c > '9')
		c = getchar();
	while(c >= '0' && c <= '9')
		re = (re << 1) + (re << 3) + c - '0',
		c = getchar();
	return re;
}

int n , k;
int fa[N * 3];
int ans;

int findroot(int x) {
	return fa[x] == x ? x : (fa[x] = findroot(fa[x]));
}
void uni(int x , int y) {
	fa[findroot(x)] = findroot(y);
}

int main() {
	n = read() , k = read();
	for(int i = 1 ; i <= n * 3 ; i++)
		fa[i] = i;
	for(int i = 1 ; i <= k ; i++) {
		int ty = read() , x = read() , y = read();
		if(x > n || y > n)	LIE
		if(findroot(x) == findroot(y)) {
			if(ty != 1)
				++ans;
			continue;
		}
		
		if(ty == 1) {
			if(findroot(x + n) == findroot(y) || findroot(x) == findroot(y + n))
				LIE
			uni(x , y);
			uni(x + n , y + n);
			uni(x + 2 * n , y + 2 * n);
		}
		else {
			if(findroot(x) == findroot(y) || findroot(x) == findroot(y + n))
				LIE
			uni(x , y + 2 * n);
			uni(x + 2 * n , y + n);
			uni(y , x + n);
			
		}
			
	}
	cout << ans;
	return 0;
}


E. 【例题5】超市购物

这题和「数据结构」第1章 二叉堆课堂过关-【例题4】工作安排,洛谷[ USACO09OPEN]Work Scheduling G 一模一样

跳过啦

题目

传送门

思路

贪心+大根堆

我们把工作截止时间从大到小排序,设当前时间为 d i d_i di​(注意此时第 i i i项工作已经截止),把截止时间为 d i d_i di​的工作全部放进堆里,设 j < i j<i j<i且 d j ≠ d i d_j\neq d_i dj​​=di​, j j j取最大值,我们若堆不为空,从堆中取出 d j − d i d_j-d_i dj​−di​个工作,把它们的 P P P累加到答案中

输出答案即可

代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define nn 100010
#define ll long long
using namespace std;
int read() {
	int re = 0;
	char c = getchar();
	while(c < '0' || c > '9') c = getchar();
	while(c >= '0' && c <= '9')
		re = (re << 1) + (re << 3) + c - '0',
		c = getchar();
	return re;
}
struct Heap {
	int siz;
	int a[nn * 2] , b[nn * 2];
	bool ty;
	inline void swap_(int x , int y) {
		int tmp;
		tmp = a[x] ; a[x] = a[y] ; a[y] = tmp;
		tmp = b[x] ; b[x] = b[y] ; b[y] = tmp;
	}
	void clear() {
		siz = 0 , ty = 0;
		memset(a , 0 , sizeof(a));
	}
	inline void push(int dat , int dat2) {
		a[++siz] = dat;
		b[siz] = dat2;
		int p = siz;
		while(p > 1 && (!(a[p >> 1] < a[p]) ^ ty))
			swap_(p >> 1 , p) , p >>= 1;
	}
	inline void pop() {
		swap_(1 , siz);
		--siz;
		int p = 1 , tmp;
		while(p * 2 <= siz && (!(a[p] < a[tmp = ( p * 2 + 1 > siz ? p * 2 : (a[p * 2] < a[p * 2 + 1] ^ ty ? p * 2 : p * 2 + 1) ) ] ) ^ ty))
			swap_(tmp , p) , p = tmp;
	}
	inline int top() {
		return siz == 0 ? 0 : a[1];
	}
	inline bool empty() {
		return siz == 0;
	}
} h;
struct node {
	int d , p;
} wk[nn];
bool cmp(node a , node b) {
	return a.d > b.d;
};
int n;
signed main() {
//	freopen("P2949_2.in" , "r" , stdin);
	h.clear();
	h.ty = 1;

	n = read();
	for(int i = 1 ; i <= n ; i++)
		wk[i].d = read() , wk[i].p = read();
	++n;
	wk[n].d = wk[n].p = 0;
	sort(wk + 1 , wk + n + 1 , cmp);
	
	ll ans = 0;
	h.push(wk[1].p , wk[1].d);
	
	int i;
	for(i = 2 ; i <= n && wk[i].d == wk[i - 1].d ; i++)
		h.push(wk[i].p , wk[i].d);
	while(i <= n) {
		int num = wk[i - 1].d - wk[i].d;
		while(num-- && !h.empty()) {
			ans += (ll)h.top();
			h.pop();
		}
		h.push(wk[i].p , wk[i].d);
		for(++i ; i <= n && wk[i].d == wk[i - 1].d ; i++)
			h.push(wk[i].p , wk[i].d);
	}
	cout << ans;
	return 0;
}

F. 【例题6】逐个击破

题目

题目背景

三大战役的平津战场上,傅作义集团在以北平、天津为中心,东起唐山西至张家口的铁路线上摆起子一字长蛇阵,并企图在溃败时从海上南逃或向西逃窜。为了就地歼敌不让其逃走,毛爷爷制定了先切断敌人东西两头退路然后再逐个歼灭敌人的战略方针。秉承伟大军事家的战略思想,作为一个有智慧的军长你,遇到了一个类似的战场局面。

题目描述

现在有N个城市,其中K个被敌方军团占领了,N个城市间有N-1条公路相连,破坏其中某条公路的代价是已知的,现在,告诉你K个敌方军团所在的城市,以及所有公路破坏的代价,请你算出花费最少的代价将这K个地方军团互相隔离开,以便第二步逐个击破敌人。

输入格式

第一行包含两个正整数n和k。

第二行包含k个整数,表示哪个城市别敌军占领。

接下来n-1行,每行包含三个正整数a,b,c,表示从a城市到b城市有一条公路,以及破坏的代价c。城市的编号从0开始。

输出格式

输出一行一个整数,表示最少花费的代价。

输入输出样例

输入 #1

5 3
1 2 4
1 0 4
1 3 8
2 1 1
2 4 3

输出 #1

4

说明/提示

【数据范围】

10%的数据:2≤n≤10;

100%的数据:2≤n≤100000,2≤k≤n,1≤c≤1000000。

思路

按边权从大到小排序每一条边,依次遍历,若一条边连接 x x x, y y y,权值为 v a l val val,若 x x x和 y y y均和被占领城市相连且与 x x x, y y y相连的被占领城市不是同一个城市,跳过,否则,将这条边加到图上**(图一开始没有任何边)**

这样,就保证了被占领的城市相互分割且删去的边权值之和最小

如何判断 x x x是否和被占领城市相连呢?很简单,并查集合并的时候强行让被占领城市做根就好了,即findroot(x)就是与 x x x相连的被占领城市

代码

#include <iostream>
#include <cstdio>
#include <algorithm>
#define N 100010
#define M 500010
#define int long long
using namespace std;
int read() {
	int re = 0;
	char c = getchar();
	bool sig = true;
	while(c < '0' || c > '9') {
		if(c == '-')	sig = false;
		c = getchar();
	}
	while(c >= '0' && c <= '9')
		re = (re << 1) + (re << 3) + c - '0',
		c = getchar();
	return sig ? re : -re;
	
}
struct node{
	int x , y , val;
}edge[M];
bool cmp(node a , node b) {
	return a.val > b.val;
}

int fa[N];
int occ[N];//occupy	(被)占领
int findroot(int x) {
	return fa[x] == x ? x : (fa[x] = findroot(fa[x]));
}
void uni(int x , int y) {
	if(findroot(x) != findroot(y)) {
		if(occ[y])
			fa[findroot(x)] = fa[y];
		else
			fa[findroot(y)] = fa[findroot(x)];
	}
}
int n , m , k;
int ans , sum;

signed main() {
	n = read() , m = read() , k = read();
	for(int i = 1 ; i <= n ; i++)
		fa[i] = i;
	for(int i = 1 ; i <= k ; i++)
		occ[read() + 1] = true;
	for(int i = 1 ; i <= m ; i++)
		edge[i].x = read() + 1 , edge[i].y = read() + 1 , sum += (edge[i].val = read());
	
	sort(edge + 1 , edge + m + 1 , cmp);
	
	for(int i = 1 ; i <= m ; i++) {
		int x = edge[i].x , y = edge[i].y , u , v;
		u = findroot(x) , v = findroot(y);
		if(occ[u] && occ[v] && u != v)
			continue;
			
		uni(u , v);
		ans += edge[i].val;
	}
	cout << sum - ans;
	return 0;
}

标签:图论,过关,val,int,查集,fa,while,read,findroot
来源: https://blog.csdn.net/weixin_46304837/article/details/115787534