码迷,mamicode.com
首页 > 编程语言 > 详细

最小树形图:朱刘算法

时间:2019-05-31 01:05:16      阅读:119      评论:0      收藏:0      [点我收藏+]

标签:需要   problem   namespace   一个   com   nbsp   show   朱刘算法   family   

Luogu P4716 【模板】最小树形图

最小树形图就是在有向图上,以某一点作为根的一棵最小生成树。

这个算法是基于贪心和缩点的思想。

步骤:

(1)先求出最短弧集合E0图上所有点的边权最小的入边的集合)

(2)如果E0不存在,则图的最小树形图也不存在,跳出循环;

(3)如果E0存在且不具有环,则E0就是最小树形图,跳出循环;

(4)如果E0存在但是存在有向环,则把这个环收缩成一个点u,形成新的图G1,然后对G1继续求其的最小树形图,返回(1)。

 

具体实现

int ans = 0; //记录加入最小树形图中的边的边权

初始化

int root; //根节点
int cnt; //环的个数 int ine[maxn]; //某一点的所有入边中的边权的最小值 int pre[maxn]; //某一点的所有入边中的边权最小的边所连接的另一点 int id[maxn]; //当前点所属的环 int vis[maxn]; //在找环的过程中,当前点是由哪一点延展过来的

注意,以上六个变量在每次缩点完成求出新图后都需要重置。

 

首先枚举每条边,看是否能更新这条边终点的最短弧。

for(int i = 1; i <= n; i++)
    ine[i] = INF;
//初始化
for(int i = 1; i <= m; i++) { int u = e[i].uu; int v = e[i].vv; if(u != v && e[i].val < ine[v]) { ine[v] = e[i].val; pre[v] = u; } }

 

枚举完成后,检查否每个点都遍历到了。如果没有,说明不存在最小树形图。

for(int i = 1; i <= n; i++)
    if(i != root && ine[i]==INF)
        return -1;

 

找出最短弧集合中的环。

cnt = 0;
for(int i = 1; i <= n; i++) {
    id[i] = 0;
    vis[i] = 0;
}
//初始化 
for(int i = 1; i <= n; i++) {
    if(i == root)continue;
    ans += ine[i];
    int v = i;
    while(vis[v]!=i && !id[v] && v!=root) {
        vis[v] = i;
        v = pre[v];
    }
    if(!id[v] && v != root) {
        id[v] = ++cnt;
        for(int u = pre[v]; u!=v; u = pre[u])
            id[u] = cnt;
    }
}
if(!cnt)break;
for(int i = 1; i <= n; i++)
    if(!id[i]) id[i] = ++cnt;

根节点无入度,不可能成环,跳过;

设当前点为v。

将统计最小树形图边权的ans加入当前点的最小弧的边权ine[v]。

从点v出发,向前寻找它的祖先(即它是由哪个点发出的边作为最小弧),如果最后又找回这一点v,说明有环存在。

  • vis[v] == i 说明这一点是本次由i点延伸过来的,即成环;
  • id[v] 表示这一点已经被染过色,已经在另一个环里;
  • 如果为根节点root,因为它没有入度,所以就会找到编号为0的点……引起各种错误。

如果排除了后两种情况,则一定是成环。

再次遍历这个环,将环中的每个点染上相同的颜色,即标出是第几个环。

全部完成后,检查环的个数。若无环,则返回答案。

没有被加入环中的点,自己单独作为一个环,方便下一步调用。

 

缩环为点,修改边权

for(int i = 1; i <= m; i++) {
    int u = e[i].uu;
    int v = e[i].vv;
    e[i].uu = id[u];
    e[i].vv = id[v];
    if(id[u] != id[v]) e[i].val -= ine[v];
}
root = id[root];
n = cnt;

枚举每条边,将该点的起点和终点均改为起点和终点所在环的序号,

即把边由连接点-点变成了环-环。

若这条边(u,v)连接两个不在同一环上的点,则边权减去ine[v]。

这部分一开始不太好理解。

技术图片

技术图片

技术图片

对于第一张图求最短弧,得到了第二张图。但是求出的集合中有弧,必须删掉环中的一条边。

把环看作一个点,需要更新连到这个环上的边的边权。因为此时已经把环中的所有边都加入了答案中,所以如果向树形图中加入边(u,v),必须删掉环中连接v的边,边权即为ine[v]。直接将当前枚举的边的边权减去ine[v],则说明若加入这条边(u,v),一定删去环上与v相连的边。

最后统计缩点后点的个数和根节点的编号,进入下一次循环。

 

完整代码如下

#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#define MogeKo qwq
using namespace std;
const int maxn = 1e5+10;
const int INF = 0x3f3f3f3f;
int n,m,root;
int ine[maxn],pre[maxn],id[maxn],vis[maxn];

struct edge {
    int uu,vv,val;
} e[maxn];

int zhuliu() {
    int ans = 0;
    while(1) {
        for(int i = 1; i <= n; i++)
            ine[i] = INF;
        for(int i = 1; i <= m; i++) {
            int u = e[i].uu;
            int v = e[i].vv;
            if(u != v && e[i].val < ine[v]) {
                ine[v] = e[i].val;
                pre[v] = u;
            }
        }
        for(int i = 1; i <= n; i++)
            if(i != root && ine[i]==INF)
                return -1;
                
        int cnt = 0; 
        for(int i = 1; i <= n; i++) {
            id[i] = 0;
            vis[i] = 0;
        }
        for(int i = 1; i <= n; i++) {
            if(i == root)continue;  
            ans += ine[i];
            int v = i;
            while(vis[v]!=i && !id[v] && v!=root) {
                vis[v] = i;
                v = pre[v];
            }
            if(!id[v] && v != root) {
                id[v] = ++cnt;
                for(int u = pre[v]; u!=v; u = pre[u])
                    id[u] = cnt;
            }
        }
        if(!cnt)break;
        for(int i = 1; i <= n; i++)
            if(!id[i]) id[i] = ++cnt;
        for(int i = 1; i <= m; i++) {
            int u = e[i].uu;
            int v = e[i].vv;
            e[i].uu = id[u];
            e[i].vv = id[v];
            if(id[u] != id[v]) e[i].val -= ine[v];
        }
        root = id[root];
        n = cnt;
    }
    return ans;
}

int main() {
    scanf("%d%d%d",&n,&m,&root);
    for(int i = 1; i <= m; i++)
        scanf("%d%d%d",&e[i].uu,&e[i].vv,&e[i].val);
    printf("%d",zhuliu());
    return 0;
}

 

最小树形图:朱刘算法

标签:需要   problem   namespace   一个   com   nbsp   show   朱刘算法   family   

原文地址:https://www.cnblogs.com/mogeko/p/10952944.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!