• 学习笔记:朱刘算法


    树形图:

    • 无环
    • 除根外每个点入度为 (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
    ", 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
    ", edmonds());
    	return 0;
    }
    
  • 相关阅读:
    从针对接口编程到依赖注入
    DataRow 数组转化成DataTable
    Math 类的方法概要
    .net控件
    字符串反转
    DataTable
    Enabled设置为False时,前景色和背景色也不改变的TextBox 并居中
    C# 四舍五入 (解决四舍六入五成双的问题)
    查询最后一条数据
    C# toString()
  • 原文地址:https://www.cnblogs.com/dmoransky/p/13624154.html
Copyright © 2020-2023  润新知