最小树形图(朱刘算法)--学习笔记

树形图的定义:一个有向图,满足从某一点u出发可以遍历全部点,并且图中不存在环(恰有点数-1条边),则称该图为一个树形图,其中u是该图的根。对于一个有向图,如果我们可以在其上生成一个包括原图所有点的树形图T,且满足T的所有边权之和最小,那么T就是原图的一个最小树形图。

  从定义上很容易联想到无向图的最小生成树。然而克鲁斯卡尔和prim算法都不能适用于有向图的情况;解决最小树形图问题最早的算法是1965年提出的朱刘算法,时间复杂度O(VE),应用比较广泛。

  根据笔者的理解,朱刘算法基于一种贪心处理之后产生的三种情况的判定:我们在原图G中找出除根外每个点的最小入边,其权值用in[v]表示。那么,我们考虑原先的点和这些入边组成的新图G':如果图中不存在环(包括自环),那么G'一定是所求的最小树形图,这是很直观的。而如果有一些点(除根u)找不到入边,那么这个图一定不存在遍历完全的方案。关键在于第三种情况:如果出现了环,我们如何进行下一步处理?

  假使现在我们遇到了一个由最小入边组成的环c。这个环上有k个点,k条边,由于每个点仅记录了一个入边,这个环与外界不存在由in[v]中的某条边连通的情况。我们现在要做的,就是拆掉这个环上的某一条边in[v],然后选择一条连接外界与点v的边,使得这个环变成一条树上的链。

  朱刘算法对这一步给出的选择策略是:我们把原先的环缩成一个点。对于指向新点(假设指向原来环上的点v)的每条边,我们令它的权值减去in[v];这样,设环上所有边的权为S,in[v] = w,最优选择的那条入边e指向v且e原来的权为cost,则:

  (S - w) + cost = S + (cost - w)

  左边表示直观地拆掉in[v]并加上那条入边e的过程,而右边是原环的值加上“按上述做法预处理过后的边权”。这个式子表明,缩点+预处理后的那张新图再次找到生成的图,对其递归地求in[v]并继续按上述三个情况判断处理,最终得到的最小树形图,与原图的最小树形图的权是一样的。这便是朱刘算法的核心。因此我们不断地找环、缩点、处理边权,直到满足没有换或发现不存在最小树形图为止。用循环和递归实现都是可以的。

  对算法复杂度的说明:每次递归都需要跑一遍所有的边来找到in[v],并且最坏情况下每次缩掉一个大小为2的环(自环在选边的时候就筛掉了,见代码),去掉一个点,则最多执行n-1次缩点就一定能得到结果了。按照更熟悉的记号,实际上复杂度就是O(mn)的。

代码:

  1. #include <iostream>  
  2. #include <cstdio>  
  3. #include <cstring>  
  4. #include <cctype>  
  5. #include <climits>  
  6. #define maxn 110  
  7. #define maxm 10010  
  8. #define inf INT_MAX  
  9. using namespace std;  
  10. int n, m, root;  
  11. long long ans;  
  12. struct E {  
  13.     int u, v, w;  
  14. } edge[maxm];  
  15. int vis[maxn], pre[maxn], in[maxn], c[maxn];  //pre数组记录前驱为找环服务,c表示某点所在的环编号
  16. void zhu_liu() {  
  17.     while (1) {  
  18.         memset(in, 127, sizeof(in));  
  19.         for (int i = 1; i <= m; ++i) {  
  20.             int u = edge[i].u, v = edge[i].v, w = edge[i].w;  
  21.             if (v != u && w < in[v])  
  22.                 in[v] = w, pre[v] = u;  
  23.         }  
  24.         in[root] = 0;  
  25.         for (int i = 1; i <= n; ++i)  
  26.             if (in[i] > 2e9) {  
  27.                 ans = -1; return;  
  28.             }  
  29.         int cnt = 0;  
  30.         memset(vis, 0, sizeof(vis));  
  31.         memset(c, 0, sizeof(c));  
  32.         for (int i = 1; i <= n; ++i) {  
  33.             ans += in[i];  //把每条边的权都预先累积下来,不是环的话一会预处理会直接减成0,不影响结果
  34.             int v = i;  
  35.             while (v != root && vis[v] != i && !c[v]) {  
  36.                 vis[v] = i;  
  37.                 v = pre[v];  
  38.             }  
  39.             if (v != root && !c[v]) {  //是环
  40.                 c[v] = ++cnt;  
  41.                 for (int u = pre[v]; u != v; u = pre[u])  
  42.                     c[u] = cnt;  
  43.             }  
  44.         }  
  45.         if (!cnt) return;  //没有环,找到答案
  46.         for (int i = 1; i <= n; ++i)  //建新图
  47.             if (!c[i]) c[i] = ++cnt;  
  48.         for (int i = 1; i <= m; ++i) {  
  49.             int u = edge[i].u, v = edge[i].v;  
  50.             edge[i].u = c[u], edge[i].v = c[v];  
  51.             if (c[u] != c[v])  
  52.                 edge[i].w -= in[v];  //处理入边权
  53.         }  
  54.         n = cnt, root = c[root];  //转到新图
  55.     }  
  56. }  
  57. int main() {  
  58.     scanf("%d %d %d", &n, &m, &root);  
  59.     int u, v, w;  
  60.     for (int i = 1; i <= m; ++i) {  
  61.         scanf("%d %d %d", &u, &v, &w);  
  62.         edge[i] = (E) {u, v, w};  
  63.     }  
  64.     zhu_liu();  
  65.     printf("%lld", ans);  
  66.     return 0;  
  67. }  

展开缩点成为原图最小树形图的算法还没有学(我估计用不到),就先写到这里。

猜你喜欢

转载自www.cnblogs.com/TY02/p/11122886.html
今日推荐