最小生成树

\(\text{luogu-2330}\)

给定一张 \(n\) 个点 \(m\) 条边的无向有权图,求这张图的最小生成树中最大边权。

\(1 \le n \le 300\)\(1 \le w \le 10^4\)


直接求最小生成树即可。

\(\text{luogu-2504}\)

给定 \(n\) 只猴子的跳跃距离 \(s_i\)\(m\) 棵树的坐标 \((x_i, y_i)\),求有多少只猴子可以通过一棵树到达所有树。

\(2 \le n \le 500\)\(2 \le m \le 1000\)\(-1000 \le x_i, y_i \le 1000\)\(1 \le s_i \le 1000\)


预处理出所有树之间的距离,跑最小生成树过程中记录最大距离。

若一只猴子的跳跃距离大于等于最大距离就累加到答案中即可。

\(\text{hdu-1301}\)

给定一张城市地图,留下最少的边权和使得图仍联通。

\(1 < n < 27\)


输入格式比较猎奇,其他没啥。

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define MAXN 10005

long long read() {
	long long x = 0, f = 1;
	char c = getchar();
	while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
	while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
	return x * f;
}

struct node { long long x, y, w; } e[MAXN];
long long n, m, fa[MAXN];

bool cmp(node l, node r) { return l.w < r.w; }

long long find(long long x) { return (fa[x] == x) ? x : fa[x] = find(fa[x]); }

long long Kruskal() {
	long long res = 0;
	sort(e + 1, e + m + 1, cmp);
	for(int i = 1; i <= m; i ++) {
		long long x = find(e[i].x), y = find(e[i].y);
		if(x == y) continue;
		fa[y] = x, res += e[i].w;
	}
	return res;
}

int main() {
	while(cin >> n) {
		m = 0; if(!n) break;
		for(int i = 0; i < n - 1; i ++) {
			long long x, y; char c;
			cin >> c >> x;
			for(int j = 1; j <= x; j ++) {
				cin >> c >> y;
				e[++ m] = {i, c - 'A', y};
			}
		}
		for(int i = 0; i < 26; i ++) fa[i] = i;
		cout << Kruskal() << "\n";
	}
	return 0;
}

\(\text{poj-1287}\)

最小生成树板子,没什么特别的。

\(\text{poj-2253}\)

给定 \(n\) 个点的坐标 \((x_i, y_i)\),要求 \(1,2\) 号点联通且路径上的距离最大值最小。

\(2 \le n \le 200\)\(0 \le x_i, y_i \le 1000\)


只需要处理 \(1,2\) 号点的连通性即可,若 \(1,2\) 已经联通则直接 break 即可。

#include<iostream>
#include<cstdio>
#include<algorithm> 
#include<cmath>
using namespace std;
#define MAXN 100005

long long read() {
	long long x = 0, f = 1;
	char c = getchar();
	while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
	while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
	return x * f;
}

struct node { long long x, y; double w; } e[MAXN];
long long n, m, x[MAXN], y[MAXN], fa[MAXN], cnt, mk;

long long find(long long x) { return (fa[x] == x) ? x : fa[x] = find(fa[x]); }

bool cmp(node l, node r) { return l.w < r.w; }

double Kruskal(long long m) {
	double res = 0;
	sort(e + 1, e + m + 1, cmp);
	for(int i = 1; i <= m; i ++) {
		long long x = find(e[i].x), y = find(e[i].y);
		if(x == y) continue;
		fa[y] = x, res = max(res, e[i].w);
		if(find(1) == find(2)) break;
	}
	return res;
}

int main() {
	while(n = read()) {
		cnt = 0;
		for(int i = 1; i <= n; i ++) x[i] = read(), y[i] = read();
		for(int i = 1; i <= n; i ++) for(int j = i + 1; j <= n; j ++)
			e[++ cnt].x = i, e[cnt].y = j, e[cnt].w = sqrt((x[i] - x[j]) * (x[i] - x[j]) + (y[i] - y[j]) * (y[i] - y[j]));
		for(int i = 1; i <= n; i ++) fa[i] = i;
		printf("Scenario #%lld\nFrog Distance = %.3lf\n\n", (++ mk), Kruskal(cnt));
	}
	return 0;
}

\(\text{hdu-1863}\)

给定一张 \(n\) 个点 \(m\) 条边的无向有权图,求最小生成树的边权和,若无法联通则输出 ?

\(1 \le n < 100\)


记录一个边数即可,若最小生成树的边数小于 \(n-1\) 则说明图不连通。

\(\text{hdu-1879}\)

给定 \(n\) 个城市和 \(\frac{n(n-1)}{2}\) 条道路,有些道路是建好的,求要求城市两两联通需要的修建费用。

\(1 < n < 100\)


若道路已经建好则不需要加入图中,只需要更新一下两个点的并查集即可。

#include<iostream>
#include<cstdio>
#include<algorithm> 
using namespace std;
#define MAXN 100005

long long read() {
	long long x = 0, f = 1;
	char c = getchar();
	while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
	while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
	return x * f;
}

struct node { long long x, y, w; } e[MAXN];
long long n, m, fa[MAXN], cnt;

long long find(long long x) { return (fa[x] == x) ? x : fa[x] = find(fa[x]); }

bool cmp(node l, node r) { return l.w < r.w; }

long long Kruskal() {
	long long res = 0;
	sort(e + 1, e + cnt + 1, cmp);
	for(int i = 1; i <= cnt; i ++) {
		long long x = find(e[i].x), y = find(e[i].y);
		if(x == y) continue;
		fa[y] = x, res += e[i].w;
	}
	return res;
}

int main() {
	while(n = read()) {
		m = (n - 1) * n / 2, cnt = 0;
		for(int i = 1; i <= n; i ++) fa[i] = i;
		for(int i = 1; i <= m; i ++) {
			long long x = read(), y = read(), w = read(), p = read();
			if(p) fa[find(y)] = find(x);
			else e[++ cnt].x = x, e[cnt].y = y, e[cnt].w = w;
		}
		cout << Kruskal() << "\n";
	}
	return 0;
}

\(\text{luogu-2872}\)

给定 \(n\) 个点的坐标和 \(m\) 条已连接的边,求图联通至少需要连接的距离和。

\(1 \le n,m \le 1000\)\(1 \le x_i, y_i \le 10^6\)\(1 \le u_i, v_i \le n\)


\(\text{hdu-1879}\) 几乎一样,不写了。

\(\text{luogu-4047}\)

给定 \(n\) 个部落的坐标,将这些部落划分成 \(k\) 个群落,求最大的最近群落距离。

\(2 \le k \le n \le 1000\)\(0 \le x_i, y_i \le 10^4\)


考虑跑一下最小生成树,对于生成树上前 \(k-1\) 大的边作为部落的分割线。

答案也就是最小生成树上第 \(n - k + 1\) 小的边权。

证明可以感性证明,不写了。

\(\text{poj-1789}\)

给定 \(n\) 个长度为 \(7\) 的字符串,定义字符串的距离为相同位置不同字符的个数。

求所有字符串距离的最小生成树的边权和。

\(2 \le n \le 2000\)


模板题,不想写。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
#define MAXN 2005

int read() {
	int x = 0, f = 1;
	char c = getchar();
	while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
	while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
	return x * f;
}

struct node { int x, y, w; } e[MAXN * MAXN];
int n, cnt, fa[MAXN], ans;
char s[MAXN][10];

bool cmp(node l, node r) { return l.w < r.w; }

int find(int x) { return (fa[x] == x) ? x : fa[x] = find(fa[x]); }

void Kruskal() {
	sort(e + 1, e + cnt + 1, cmp);
	for(int i = 1; i <= cnt; i ++) {
		int x = find(e[i].x), y = find(e[i].y);
		if(x == y) continue;
		fa[y] = x, ans += e[i].w;
	}
	return;
}

int main() {
	while(n = read()) {
		cnt = ans = 0;
		for(int i = 1; i <= n; i ++) cin >> (s[i] + 1);
		for(int i = 1; i <= n; i ++) for(int j = i + 1; j <= n; j ++) {
			int res = 0;
			for(int k = 1; k <= 7; k ++) if(s[i][k] != s[j][k]) res ++;
			e[++ cnt].x = i, e[cnt].y = j, e[cnt].w = res;
		}
		for(int i = 1; i <= n; i ++) fa[i] = i;
		Kruskal(); 
		cout << "The highest possible quality is 1/" << ans << ".\n";
	}
	return 0;
}

\(\text{poj-2485}\)

求最小生成树上的最大边权。

\(3 \le n \le 500\)


模板题。

\(\text{luogu-1546}\)

求最小生成树的边权和。

\(3 \le n \le 100\)


模板题。

\(\text{hdu-4081}\)

给出 \(n\) 个点的城市坐标与城市的人口,要求将所有城市连通起来,其中有一条道路是魔法道路没有边权,要求这条道路连接的两城市的人口数和为 \(A\),其余道路权值和为 \(B\),求 \(\frac{A}{B}\) 的最大值。

\(1 \le T \le 10\)\(2 \le n \le 1000\)\(0 \le x_i, y_i \le 1000\)\(0 < p < 10^5\)


次小生成树思想,枚举并删边。

先根据城市坐标求出任两个城市相连道路的边权,然后根据边权求最小生成树,由于连接两个人口数最大的城市的道路没有边权,因此删掉所枚举的边后剩余的边权和是固定的,即 \(B\) 是固定的,因而可以枚举所有边,确立边两端的点的人口数之和,寻找最大的人口数之和即 \(A\),除枚举的边之外的边权和即 \(B\)

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<vector>
using namespace std;
#define MAXN 1005

int read() {
	int x = 0, f = 1;
	char c = getchar();
	while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
	while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
	return x * f;
}

int T, n, x[MAXN], y[MAXN], p[MAXN], fa[MAXN], cnt, s[MAXN];
struct node { int x, y, w; } e[MAXN * MAXN];
vector<long long> v[MAXN];
bool vis[MAXN];
double ans;

bool cmp(node l, node r) { return l.w < r.w; }

int find(int x) { return (fa[x] == x) ? x : fa[x] = find(fa[x]); }

void Kruskal() {
	sort(e + 1, e + cnt + 1, cmp);
	long long res = 0;
	for(int i = 1; i <= cnt; i ++) {
		long long x = find(e[i].x), y = find(e[i].y);
		if(x == y) continue;
		fa[y] = x, s[++ res] = i;
		v[e[i].x].push_back(e[i].y);
		v[e[i].y].push_back(e[i].x);
	}
	return;
}

double dfs(long long x, double res) {
	vis[x] = true, res = max(res, 1.0 * p[x]);
	for(auto y : v[x]) if(!vis[y]) res = dfs(y, res);
	return res;
}

int main() {
	T = read();
	while(T --) {
		n = read(), cnt = 0, ans = -1e18;
		for(int i = 1; i <= n; i ++) 
			x[i] = read(), y[i] = read(), p[i] = read();
		for(int i = 1; i <= n; i ++) v[i].clear();
		for(int i = 1; i <= n; i ++) for(int j = i + 1; j <= n; j ++)
			e[++ cnt].x = i, e[cnt].y = j, e[cnt].w = (x[i] - x[j]) * (x[i] - x[j]) + (y[i] - y[j]) * (y[i] - y[j]);
		for(int i = 1; i <= n; i ++) fa[i] = i;
		Kruskal(); double dis = 0;
		for(int i = 1; i < n; i ++) dis += sqrt(e[s[i]].w);
		for(int i = 1; i < n; i ++) {
			for(int j = 1; j <= n; j ++) vis[j] = false;
			vis[e[s[i]].y] = true;
			double y = dfs(e[s[i]].x, 0) + dfs(e[s[i]].y, 0);
			ans = max(ans, y / (dis - sqrt(e[s[i]].w)));
		}
		printf("%.2lf\n", ans);
	}
	return 0;
}

\(\text{poj-2728}\)

给定 \(n\) 个点的坐标和点权,任意两点之间的边的价值是它们的距离,费用是两点权值之差的绝对值,求该图的一棵生成树,使得该树所有边的费用之和与价值之和的比值最小(只需求这个比值即可)。

\(2 \le n \le 1000\)\(0 \le x_i, y_i \le 10^4\)\(0 \le w_i \le 10^9\)


事实上就是求下式的最小值,\(w_i \in \{0, 1\}\)

\[\frac{\sum\limits_{i=1}^n a_i \times w_i}{\sum\limits_{i=1}^n b_i \times w_i} \]

这类问题称为分数规划,通用方法就是二分答案,设当前二分的答案为 \(mid\),则:

\[\begin{align} \frac{\sum a_i \times w_i}{\sum b_i \times w_i} &\ge mid \\ \sum a_i \times w_i - mid \times \sum b_i \times w_i &\ge 0 \\ \sum w_i \times (a_i - mid \times b_i) &\ge 0 \end{align} \]

即不等式成立则说明 \(mid\) 合法,因此,这道题要求最小值,也就是求 \(mid\) 合法最大值。

于是二分答案,然后用 \(\text{Prim}\) 算法 \(O(n^2)\) \(\text{check}\) 即可。

总体时间复杂度为 \(O(n^2 \log (\sum w_i))\)

注意:这道题 \(\text{Kruskal}\)\(\text{TLE}\);不要用 \(\text{memset()}\) 初始化 \(\text{double}\) 变量 \(\dots\)

#include<iostream>
#include<cstdio>
#include<cmath>
#include<vector>
#include<cstring>
using namespace std;
#define MAXN 5005

int read() {
	int x = 0, f = 1;
	char c = getchar();
	while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
	while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
	return x * f;
}

int n, x[MAXN], y[MAXN], p[MAXN];
double dis[MAXN];
bool vis[MAXN];

bool Prim(double mid) {
	for(int i = 1; i <= n; i ++) dis[i] = 1e18, vis[i] = false;
	dis[1] = 0; double res = 0;
	for(int i = 1; i < n; i ++) {
		int mk = 0;
		for(int j = 1; j <= n; j ++) if(!vis[j] && 
			(dis[j] < dis[mk] || !mk)) mk = j;
		vis[mk] = true;
		for(int j = 1; j <= n; j ++) if(!vis[j])
			dis[j] = min(dis[j], abs(p[j] - p[mk]) - mid * sqrt((x[mk] - x[j]) * (x[mk] - x[j]) + (y[mk] - y[j]) * (y[mk] - y[j])));
	}
	for(int i = 2; i <= n; i ++) res += dis[i];
	return (res >= 0);
}

int main() {
	while(cin >> n) {
		if(!n) break;
		for(int i = 1; i <= n; i ++) cin >> x[i] >> y[i] >> p[i];
		double l = 0, r = 1e9, eps = 1e-8;
		while(r - l >= eps) {
			double mid = (l + r) / 2.0;
			if(Prim(mid)) l = mid;
			else r = mid;
		}
		printf("%.3lf\n", l);
	}
	return 0;
}

\(\text{luogu-1669}\)

求最大生成树的边权和。

\(2 \le n \le 1000\)\(1 \le m \le 2 \times 10^4\)\(1 \le w_i \le 10^5\)


模板题。

\(\text{hdu-4750}\)

给定 \(n\) 个点 \(m\) 条边的无向图。

\(q\) 个询问 \(x\),求有多少二元组 \((i, j)\) 满足 \(i\)\(j\) 的所有路径上的最大边权的最小值大于等于 \(x\)

\(1 \le n \le 10^4\)\(1 \le m \le 5 \times 10^5\)\(1 \le q \le 10^5\)


\((i, j)\) 所有路径上的最大边权的最小值实际上就是 \(i\)\(j\) 在最小生成树上路径的最大边权。

注意到每条边的权值都是不相等的,那么最小生成树就是确定的。

假设当前 \(\text{MST}\) 的边的权值是 \(f_i\)\(\text{Kruskal}\) 的并查集中维护一个 \(cnt_i\),表示以节点 \(i\) 为根的集合的节点个数,那么两个集合 \(x\)\(y\) 合并,点对数就增加 \(cnt_{fa_x} \times cnt_{fa_y} \times 2\)

\(s\) 为点对数的累计和,那么询问 \(x\) 小于等于下一个 \(\text{MST}\) 中的边 \(f_{i+1}\) 的值就是 \(2n(n-1) - s\)

离线处理询问即可。

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define MAXN 500005

long long read() {
	long long x = 0, f = 1;
	char c = getchar();
	while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
	while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
	return x * f;
}

long long n, m, q, fa[MAXN], cnt[MAXN], ans[MAXN];
struct node { long long x, y, w; } e[MAXN];
struct query { long long x, id; } a[MAXN];

bool cmp1(query l, query r) { return l.x < r.x; }

bool cmp2(node l, node r) { return l.w < r.w; }

long long find(long long x) { return (fa[x] == x) ? x : fa[x] = find(fa[x]); }

int main() {
	while(cin >> n >> m) {
		for(int i = 1; i <= m; i ++) 
			e[i].x = read() + 1, e[i].y = read() + 1, e[i].w = read();
		q = read();
		for(int i = 1; i <= q; i ++) a[i].x = read(), a[i].id = i;
		sort(a + 1, a + q + 1, cmp1);
		sort(e + 1, e + m + 1, cmp2);
		for(int i = 1; i <= n; i ++) fa[i] = i, cnt[i] = 1;
		long long t = 0, k = 1, j = 1;
		for(int i = 1; i <= m && k < n; i ++) {
			long long x = find(e[i].x), y = find(e[i].y);
			if(x == y) continue;
			while(j <= q && a[j].x <= e[i].w)
				ans[a[j].id] = n * (n - 1) - t, j ++;
			t += cnt[x] * cnt[y] * 2;
			fa[x] = y, cnt[y] += cnt[x], k ++;
		}
		while(j <= q) ans[a[j].id] = n * (n - 1) - t, j ++;
		for(int i = 1; i <= q; i ++) cout << ans[i] << "\n";
	}
	return 0;
}

\(\text{hdu-3938}\)

两点之间建立传送门需要的能量为他们之间所有路径里最小的 \(T\),一条路径的T为该路径上最长的边的长度。现在 \(q\) 个询问,问 \(x\) 能量可以选择多少种不同点对?

\(1 < n \le 10^4\)\(1 \le m \le 5 \times 10^4\)\(0 \le x \le 10^8\)


因为小的能量找出的点对,在大的能量下肯定也能建立传送门,因此把询问记下来,按询问的能量从小到大计算,这样离线处理。

从小到大枚举添加每条能量不超过当前能量且还没枚举过的边,

如果它连接的两个点属于不同联通块,就加上这条边。

能选择的点对就有两个联通块的点数之积那么多种。

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define MAXN 500005

long long read() {
	long long x = 0, f = 1;
	char c = getchar();
	while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
	while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
	return x * f;
}

long long n, m, q, fa[MAXN], cnt[MAXN], ans[MAXN];
struct node { long long x, y, w; } e[MAXN];
struct query { long long x, id; } a[MAXN];

bool cmp1(query l, query r) { return l.x < r.x; }

bool cmp2(node l, node r) { return l.w < r.w; }

long long find(long long x) { return (fa[x] == x) ? x : fa[x] = find(fa[x]); }

int main() {
	while(cin >> n >> m >> q) {
		for(int i = 1; i <= m; i ++) 
			e[i].x = read(), e[i].y = read(), e[i].w = read();
		for(int i = 1; i <= q; i ++) a[i].x = read(), a[i].id = i;
		sort(a + 1, a + q + 1, cmp1);
		sort(e + 1, e + m + 1, cmp2);
		for(int i = 1; i <= n; i ++) fa[i] = i, cnt[i] = 1;
		long long l = 1;
		for(int i = 1; i <= q; i ++) {
			ans[a[i].id] = ans[a[i - 1].id];
			while(l <= m && e[l].w <= a[i].x) {
				long long x = find(e[l].x), y = find(e[l].y);
				if(x != y) {
					fa[y] = x, ans[a[i].id] += cnt[x] * cnt[y];
					cnt[x] += cnt[y];
				}
				l ++;
			}
		}
		for(int i = 1; i <= q; i ++) cout << ans[i] << "\n";
	}
	return 0;
}

\(\text{hdu-6187}\)

\(n\) 个塔和 \(m\) 面墙,塔和塔之间有墙,这些墙把城市分隔开了。

拆第 \(i\) 面墙需要花费 \(w_i\) 代价,求使得所有城市连通的最小代价。

墙只在端点相交。可以保证没有墙连接相同的塔,也没有两面墙连接相同的一对塔。也就是说,由墙和塔形成的给定图不包含任何多重边或自环。

\(3 \le n \le 10^5\)\(1 \le m \le 2 \times 10^5\)\(|x_i|,|y_i| \le 10^5\)\(0 \le w_i \le 10^4\)


诈骗题,实际上塔的坐标没有一点用。

求最小代价联通城市,等价于求最大代价联通塔,跑最大生成树即可。

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define MAXN 200005

long long read() {
	long long x = 0, f = 1;
	char c = getchar();
	while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
	while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
	return x * f;
}

struct node { long long x, y, w; } e[MAXN];
long long n, m, x, y, fa[MAXN];

bool cmp(node l, node r) { return l.w > r.w; }

long long find(long long x) { return (fa[x] == x) ? x : fa[x] = find(fa[x]); }

pair<long long, long long> Kruskal() {
	for(int i = 1; i <= n; i ++) fa[i] = i;
	sort(e + 1, e + m + 1, cmp);
	long long res = 0, cnt = 0;
	for(int i = 1; i <= m; i ++) {
		long long x = find(e[i].x), y = find(e[i].y);
		if(x == y) continue;
		fa[y] = x, res += e[i].w, cnt ++;
	}
	return {cnt, res};
}

int main() {
	while(cin >> n >> m) {
		for(int i = 1; i <= n; i ++) x = read(), y = read();
		for(int i = 1; i <= m; i ++) 
			e[i].x = read(), e[i].y = read(), e[i].w = read();
		long long ans = 0;
		for(int i = 1; i <= m; i ++) ans += e[i].w;
		pair<long long, long long> p = Kruskal();
		cout << m - p.first << " " << ans - p.second << "\n";
	}
	return 0;
}
posted @ 2025-12-10 21:25  So_noSlack  阅读(8)  评论(0)    收藏  举报