最小树形图

最小树形图

1.算法分析

有向图上的最小生成树(Directed Minimum Spanning Tree)称为最小树形图。常用的算法是朱刘算法(也称 Edmonds 算法),可以在 O ( n m ) O(nm) O(nm) 时间内解决最小树形图问题。

该算法最后能够找到一个从根出发,能够走到任意一个点的一个最小权值有向的树。

流程

  1. 对于每个点,选择它入度最小的那条边
  2. 如果没有环,算法终止;否则进行缩环并更新其他点到环的距离。
  3. 缩点时:对于环内部的边,删去;对于终点在环内的,边权变为w外-w环内。

2.模板

#include <bits/stdc++.h>
#define LL long long
using namespace std;
const int N = 100 + 5;
const int mod = 1000000000 + 7;
const double eps = 1e-8;
typedef pair<int, int> PII;
int id[N], vis[N], pre[N], pos;  // id记录每个点属于哪个环,vis用来判断几个点是否处于一个环内,
                                 // pre记录每个点的前一个节点,pos记录虚节点
double dis[N], INF = 1e17;  // dis记录每个点到最小生成树的距离
PII point[N];
// 定义边的结构
struct node {
    int u, v;
    double cost;
} edge[N * N];
int n, m;
// 朱刘算法
// root:根节点(记得下标从0开始)
// V点数0~V-1
// E边数0~E-1
double zhuliu(int root, int V, int E) {
    double sum = 0;  // 计算当前最小树形图的边权值和
    while (true) {
        // 把每个点到最小生成树的距离置为无穷
        for (int i = 0; i < V; i++) {
            dis[i] = INF;
        }

        //找最小入边
        for (int i = 0; i < E; i++) {
            int u = edge[i].u, v = edge[i].v;
            if (u != v && dis[v] > edge[i].cost) {
                dis[v] = edge[i].cost;
                pre[v] = u;
                if (u == root) {
                    pos = i;
                }
            }
        }

        //某点不存在入边,算法结束
        for (int i = 0; i < V; i++) {
            if (dis[i] == INF && i != root) return -1;
        }
        int cnt = 0;  // 记录当前这张图内点的个数
        memset(id, -1, sizeof id);
        memset(vis, -1, sizeof vis);
        dis[root] = 0;  // 把根节点放入最小树形图

        //找环
        for (int i = 0; i < V; i++) {
            int v = i;
            sum += dis[i];
            while (id[v] == -1 && vis[v] != i && v != root) {
                vis[v] = i;
                v = pre[v];
            }

            //找到环的时候缩点编号
            if (id[v] == -1 && v != root) {
                for (int u = pre[v]; u != v; u = pre[u]) {
                    id[u] = cnt;  // 把u点放入编号为cnt的环内
                }
                id[v] = cnt++;
            }
        }

        //如果没有环,则以找到最小树形图,算法结束
        if (!cnt) break;

        //把余下的不在环里的点编号
        for (int i = 0; i < V; i++) {
            if (id[i] == -1) id[i] = cnt++;
        }

        //更新距离
        for (int i = 0; i < E; i++) {
            int u = edge[i].u, v = edge[i].v;
            edge[i].u = id[u];
            edge[i].v = id[v];
            if (id[u] != id[v]) edge[i].cost -= dis[v];
        }
        V = cnt;          // 新图的点数为cnt
        root = id[root];  // 新图的根为原来的根所在的缩点集
    }
    return sum;
}

double get_dist(int x, int y) {
    return sqrt(((double)point[x].first - point[y].first) * ((double)point[x].first - point[y].first) + ((double)point[x].second - point[y].second) * ((double)point[x].second - point[y].second) );
}

int main() {
    while(scanf("%d%d", &n, &m) != EOF) {
        for (int i = 1; i <= n; ++i) cin >> point[i].first >> point[i].second;
        for (int i = 0; i < m; ++i) {
            int a, b;
            cin >> a >> b;
            edge[i] = {a - 1, b - 1, get_dist(a, b)};
        }
        double t = zhuliu(0, n, m);
        if (t == -1) cout << "poor snoopy\n";
        else printf("%.2lf\n", t);
    }
    return 0;
}

3.典型例题

3.1 定根最小树形图

acwing2417. 指挥网络

题意: 给定一张n个点m条边的有向图,边的权值为double型,要求找到这张图的最小树形图。 1 < = N < = 100 , 1 < = M < = 1 0 4 , 0 < x i , y i < = 10000 , 1 < = a , b < = N , a ≠ b 1<=N<=100, 1<=M<=10^4,0<xi,yi<=10000,1<=a,b<=N, a \neq b 1<=N<=100,1<=M<=104,0<xi,yi<=10000,1<=a,b<=N,a=b

题解: 模板题

代码:

#include <bits/stdc++.h>
#define LL long long
using namespace std;
const int N = 100 + 5;
const int mod = 1000000000 + 7;
const double eps = 1e-8;
typedef pair<int, int> PII;
int id[N], vis[N], pre[N], pos;  // id记录每个点属于哪个环,vis用来判断几个点是否处于一个环内,
                                 // pre记录每个点的前一个节点,pos记录虚节点
double dis[N], INF = 1e17;  // dis记录每个点到最小生成树的距离
PII point[N];
// 定义边的结构
struct node {
    int u, v;
    double cost;
} edge[N * N];
int n, m;
// 朱刘算法
// root:根节点(记得下标从0开始)
// V点数0~V-1
// E边数0~E-1
double zhuliu(int root, int V, int E) {
    double sum = 0;  // 计算当前最小树形图的边权值和
    while (true) {
        // 把每个点到最小生成树的距离置为无穷
        for (int i = 0; i < V; i++) {
            dis[i] = INF;
        }

        //找最小入边
        for (int i = 0; i < E; i++) {
            int u = edge[i].u, v = edge[i].v;
            if (u != v && dis[v] > edge[i].cost) {
                dis[v] = edge[i].cost;
                pre[v] = u;
                if (u == root) {
                    pos = i;
                }
            }
        }

        //某点不存在入边,算法结束
        for (int i = 0; i < V; i++) {
            if (dis[i] == INF && i != root) return -1;
        }
        int cnt = 0;  // 记录当前这张图内点的个数
        memset(id, -1, sizeof id);
        memset(vis, -1, sizeof vis);
        dis[root] = 0;  // 把根节点放入最小树形图

        //找环
        for (int i = 0; i < V; i++) {
            int v = i;
            sum += dis[i];
            while (id[v] == -1 && vis[v] != i && v != root) {
                vis[v] = i;
                v = pre[v];
            }

            //找到环的时候缩点编号
            if (id[v] == -1 && v != root) {
                for (int u = pre[v]; u != v; u = pre[u]) {
                    id[u] = cnt;  // 把u点放入编号为cnt的环内
                }
                id[v] = cnt++;
            }
        }

        //如果没有环,则以找到最小树形图,算法结束
        if (!cnt) break;

        //把余下的不在环里的点编号
        for (int i = 0; i < V; i++) {
            if (id[i] == -1) id[i] = cnt++;
        }

        //更新距离
        for (int i = 0; i < E; i++) {
            int u = edge[i].u, v = edge[i].v;
            edge[i].u = id[u];
            edge[i].v = id[v];
            if (id[u] != id[v]) edge[i].cost -= dis[v];
        }
        V = cnt;          // 新图的点数为cnt
        root = id[root];  // 新图的根为原来的根所在的缩点集
    }
    return sum;
}

double get_dist(int x, int y) {
    return sqrt(((double)point[x].first - point[y].first) * ((double)point[x].first - point[y].first) + ((double)point[x].second - point[y].second) * ((double)point[x].second - point[y].second) );
}

int main() {
    while(scanf("%d%d", &n, &m) != EOF) {
        for (int i = 1; i <= n; ++i) cin >> point[i].first >> point[i].second;  // 读入点
        for (int i = 0; i < m; ++i) {
            int a, b;
            cin >> a >> b;
            edge[i] = {a - 1, b - 1, get_dist(a, b)};  // 读入边
        }
        double t = zhuliu(0, n, m);  // 朱刘算法
        if (t == -1) cout << "poor snoopy\n";  // 无解
        else printf("%.2lf\n", t);  // 最小树形图的最小权值
    }
    return 0;
}

3.2 不定根最小树形图

hdu2121 Ice_cream’s world II

题意: 有n个城市,问能否在者n个城市中选一个城市作为首都,要求首都都能到其他城市,道路花费要最少,且道路都是单向的。打印最少花费和选出来的首都。

题解: 本题要找一个根,同时要求出最小树形图的权值和。可以虚拟一个虚根,如果以虚根为根生成的最小树形图权值和ans减去不包含虚根的全部权值和sum为大于全部权值和,那么不存在这样一个虚根(因为最多只会有一条虚边);反之,存在,则真正的最小树形图的权值和为 ans - sum,根节点为与虚根相连的那个点pos - m(n节点对应m边,n+1节点对应m+1边)

代码:

#include <stdio.h>
#include <cstdlib>
#include <cstring>
#include <iostream>
#define LL long long
using namespace std;
const int N = 1000 + 5;
const int mod = 1000000000 + 7;
const double eps = 1e-8;
int id[N], vis[N], pre[N],
    pos;  // id记录每个点属于哪个环,vis用来判断几个点是否处于一个环内,
          // pre记录每个点的前一个节点,pos记录虚节点
LL dis[N], INF = 1e17;  // dis记录每个点到最小生成树的距离
// 定义边的结构
struct node {
    int u, v, cost;
} edge[N * N];

// 朱刘算法
// root:根节点
// V点数0~V-1
// E边数0~E-1
LL zhuliu(int root, int V, int E) {
    LL sum = 0;  // 计算当前最小树形图的边权值和
    while (true) {
        // 把每个点到最小生成树的距离置为无穷
        for (int i = 0; i < V; i++) {
            dis[i] = INF;
        }

        //找最小入边
        for (int i = 0; i < E; i++) {
            int u = edge[i].u, v = edge[i].v;
            if (u != v && dis[v] > edge[i].cost) {
                dis[v] = edge[i].cost;
                pre[v] = u;
                if (u == root) {
                    pos = i;
                }
            }
        }

        //某点不存在入边,算法结束
        for (int i = 0; i < V; i++) {
            if (dis[i] == INF && i != root) return -1;
        }
        int cnt = 0;  // 记录当前这张图内点的个数
        memset(id, -1, sizeof id);
        memset(vis, -1, sizeof vis);
        dis[root] = 0;  // 把根节点放入最小树形图

        //找环
        for (int i = 0; i < V; i++) {
            int v = i;
            sum += dis[i];
            while (id[v] == -1 && vis[v] != i && v != root) {
                vis[v] = i;
                v = pre[v];
            }

            //找到环的时候缩点编号
            if (id[v] == -1 && v != root) {
                for (int u = pre[v]; u != v; u = pre[u]) {
                    id[u] = cnt;  // 把u点放入编号为cnt的环内
                }
                id[v] = cnt++;
            }
        }

        //如果没有环,则以找到最小树形图,算法结束
        if (!cnt) break;

        //把余下的不在环里的点编号
        for (int i = 0; i < V; i++) {
            if (id[i] == -1) id[i] = cnt++;
        }

        //更新距离
        for (int i = 0; i < E; i++) {
            int u = edge[i].u, v = edge[i].v;
            edge[i].u = id[u];
            edge[i].v = id[v];
            if (id[u] != id[v]) edge[i].cost -= dis[v];
        }
        V = cnt;          // 新图的点数为cnt
        root = id[root];  // 新图的根为原来的根所在的缩点集
    }
    return sum;
}

int main() {
    int n, m;
    LL ans, temp;
    while (~scanf("%d%d", &n, &m)) {
        temp = 1;

        // 读入边的信息
        for (int i = 0; i < m; i++) {
            scanf("%d%d%d", &edge[i].u, &edge[i].v, &edge[i].cost);
            temp += edge[i].cost;
        }

        //以n为虚拟节点
        for (int i = m; i < n + m; i++) {
            edge[i].u = n;
            edge[i].v = i - m;
            edge[i].cost = temp;
        }

        //  得到以虚节点为根节点的最小树形图的边权值和
        ans = zhuliu(n, n + 1, n + m);
        if (ans == -1 || ans - temp >= temp)
            puts("impossible\n");
        else
            printf("%lld %d\n\n", ans - temp, pos - m);
    }
    return 0;
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值