学习笔记:朱刘算法

树形图:

  • 无环
  • 除根外每个点入度为 \(1\) (或:每个点父节点唯一)

最小树形图问题:找出总边权和最小的树形图


朱刘算法解决最小树形图问题。

算法流程(每次迭代):

  1. 对于除根外每个点,找出该点入边中权值最小的边,把权加到答案中。

  2. 判断选出的边是否存在环。若无环,退出,找到最小树形图,若有环,继续执行步骤 3。

  3. 将所有环缩点,构造一个新图,对于旧图的每条边:

    • 若这条边在环内,删去
    • 否则:若该边终点在环内,权值改为原先权值 - 该终点当前入边(在这个环上)的权: \(W - W'\)

算法正确性:

  1. 对于每个环而言,至少去掉一条边。

  2. 对于每个环而言,必然存在一个最优解,只去一条边(若选了两条,那么把其中一条边选回环上,答案不

    会变差)。

  3. 算法即在满足1、2性质的所有树形图中求最优解。

  4. 选改动过的权值,若当前边 = 该终点当前选的边,那么权值换为 \(0\),相当于标记已经选完;否则,选这条边相当于改变一个点的入边,可以映射到左边的一个树形图。

从某种角度上来说,朱刘算法就是带后悔的贪心。


算法复杂度:

\(O(nm)\),每次迭代一次点数至少会 \(-1\)

板子

\(n\) 是点数,\(m\) 是边数,\(e\) 结构体数组是每条边 \((u, v, w)\)\(ans\) 是答案。

\(in\) 是每个点入边边权,\(pre\) 是每个点入边的起点编号,\(id\) 是缩点后的编号,\(vis\) 是找环辅助数组。

找环:

  • 每次循环一个点 \(i\),从 \(v = i\) 开始一直走 \(pre\),直到 \(vis[v]\) 非空,如果走的路径构成一个环,即停下来以后 \(vis[v] = i\),这样把这个环上的点赋值 \(id\) 即可。(网上很多找环都是 \(O(n^2)\) 的。。)
double inline edmonds() {
	double ans = 0;
	while (true) {
		for (int i = 1; i <= n; i++) in[i] = INF;
		memset(vis, 0, sizeof vis);
		memset(id, 0, sizeof id);
		for (int i = 1; i <= m; i++) 
			if (e[i].w < in[e[i].v]) in[e[i].v] = e[i].w, pre[e[i].v] = e[i].u;
		for (int i = 1; i <= n; i++)
			if (in[i] == INF && i != rt) return -1;
		col = 0;
		for (int i = 1; i <= n; i++) {
			if (i == rt) continue;
			ans += in[i];
			int v = i;
			while (!vis[v] && !id[v] && v != rt)
				vis[v] = i, v = pre[v];
 			if (v != rt && vis[v] == i) {
 				id[v] = ++col;
 				for (int x = pre[v]; x != v; x = pre[x]) id[x] = col;
 			}
		}
		if (!col) break;
		for (int i = 1; i <= n; i++) if (!id[i]) id[i] = ++col;
		int tot = 0;
		for (int i = 1; i <= m; i++) {
			int a = id[e[i].u], b = id[e[i].v];
			if (a == b) continue;
			e[++tot] = (E) { a, b, e[i].w - in[e[i].v] };
		}
		m = tot, n = col, rt = id[rt];
	}
	return ans;
}

模板题

AcWing 2417. 指挥网络

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;

typedef long long LL;

const int N = 105, M = 10005;

const double INF = 1e100;

int n, m, rt = 1, X[N], Y[N], col;

double in[N];

int vis[N], id[N], pre[N];

struct E{
	int u, v;
	double w;
} e[M];

double inline edmonds() {
	double ans = 0;
	while (true) {
		for (int i = 1; i <= n; i++) in[i] = INF;
		memset(vis, 0, sizeof vis);
		memset(id, 0, sizeof id);
		for (int i = 1; i <= m; i++) 
			if (e[i].w < in[e[i].v]) in[e[i].v] = e[i].w, pre[e[i].v] = e[i].u;
		for (int i = 1; i <= n; i++)
			if (in[i] == INF && i != rt) return -1;
		col = 0;
		for (int i = 1; i <= n; i++) {
			if (i == rt) continue;
			ans += in[i];
			int v = i;
			while (!vis[v] && !id[v] && v != rt)
				vis[v] = i, v = pre[v];
 			if (v != rt && vis[v] == i) {
 				id[v] = ++col;
 				for (int x = pre[v]; x != v; x = pre[x]) id[x] = col;
 			}
		}
		if (!col) break;
		for (int i = 1; i <= n; i++) if (!id[i]) id[i] = ++col;
		int tot = 0;
		for (int i = 1; i <= m; i++) {
			int a = id[e[i].u], b = id[e[i].v];
			if (a == b) continue;
			e[++tot] = (E) { a, b, e[i].w - in[e[i].v] };
		}
		m = tot, n = col, rt = id[rt];
	}
	return ans;
}

int main() {
	while (~scanf("%d%d", &n, &m)) {
		rt = 1;
		for (int i = 1; i <= n; i++) scanf("%d%d", X + i, Y + i);
		int tot = 0;
		for (int i = 1; i <= m; i++) {
			int a, b; scanf("%d%d", &a, &b);
			e[++tot] = (E) { a, b, sqrt(((LL)X[a] - X[b]) * (X[a] - X[b]) + ((LL)Y[a] - Y[b]) * (Y[a] - Y[b])) };
		}
		m = tot;
		double res = edmonds();
	 	if (res == -1) puts("poor snoopy");
	 	else printf("%.2f\n", res);	
	}
	return 0;
}

模板题 2

落谷 P4716 【模板】最小树形图

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;

typedef long long LL;

const int N = 105, M = 10005, INF = 1e9;

int n, m, rt = 1, X[N], Y[N], col, in[N];

int vis[N], id[N], pre[N];

struct E{
	int u, v, w;
} e[M];

int inline edmonds() {
	int ans = 0;
	while (true) {
		for (int i = 1; i <= n; i++) in[i] = INF;
		memset(vis, 0, sizeof vis);
		memset(id, 0, sizeof id);
		for (int i = 1; i <= m; i++) 
			if (e[i].w < in[e[i].v]) in[e[i].v] = e[i].w, pre[e[i].v] = e[i].u;
		for (int i = 1; i <= n; i++)
			if (in[i] == INF && i != rt) return -1;
		col = 0;
		for (int i = 1; i <= n; i++) {
			if (i == rt) continue;
			ans += in[i];
			int v = i;
			while (!vis[v] && !id[v] && v != rt)
				vis[v] = i, v = pre[v];
 			if (v != rt && vis[v] == i) {
 				id[v] = ++col;
 				for (int x = pre[v]; x != v; x = pre[x]) id[x] = col;
 			}
		}
		if (!col) break;
		for (int i = 1; i <= n; i++) if (!id[i]) id[i] = ++col;
		int tot = 0;
		for (int i = 1; i <= m; i++) {
			int a = id[e[i].u], b = id[e[i].v];
			if (a == b) continue;
			e[++tot] = (E) { a, b, e[i].w - in[e[i].v] };
		}
		m = tot, n = col, rt = id[rt];
	}
	return ans;
}

int main() {
	scanf("%d%d%d", &n, &m, &rt);
	int tot = 0;
	for (int i = 1; i <= m; i++) {
		int a, b, c; scanf("%d%d%d", &a, &b, &c);
		if (b != rt && a != b) e[++tot] = (E) { a, b, c };
	}
	m = tot;
	printf("%d\n", edmonds());
	return 0;
}
posted @ 2020-09-06 22:55  DMoRanSky  阅读(924)  评论(0编辑  收藏  举报