POJ - 3164 Command Network(朱刘算法 最小树形图)

就是给有向带权图中指定一个特殊的点root,求一棵以root为根的有向生成树T,并且T中所有边的总权值最小。解决这种问题要用到朱刘算法。朱刘算法有以下五步:

1、找到除了root以为其他点的权值最小的入边。用In[i]记录

2、如果出现除了root以为存在其他孤立的点,则不存在最小树形图。

3、找到图中所有的环,并对环进行缩点,重新编号。
4、更新其他点到环上的点的距离,如:(in【j】表示到j结点的入度中最小的权值)

环中的点有(Vk1,Vk2,… ,Vki)总共i个,用缩成的点叫Vk替代,则在压缩后的图中,其他所有不在环中点v到Vk的距离定义如下:
gh[v][Vk]=min { gh[v][Vkj]-in[Vkj] } (1<=j<=i)

而Vk到v的距离为
gh[Vk][v]=min { gh[Vkj][v] }              (1<=j<=i)

 5、重复3,4知道没有环为止。

为什么出边的权不变,入边的权要减去in [u]?

    对于新图中的最小树形图T,设指向人工节点的边为e。将人工节点展开以后,e指向了一个环。假设原先e是指向u的,这个时候我们将环上指向u的边 in[u]删除,这样就得到了原图中的一个树形图。我们会发现,如果新图中e的权w'(e)是原图中e的权w(e)减去in[u]权的话,那么在我们删除掉in[u],并且将e恢复为原图状态的时候,这个树形图的权仍然是新图树形图的权加环的权,而这个权值正是最小树形图的权值。所以在展开节点之后,我们得到的仍然是最小树形图。逐步展开所有的人工节点,就会得到初始图的最小树形图了。

流程如下图:

#include <iostream>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <string>
typedef long long LL;
using namespace std;
const int MAXV = 100;
const int MAXE = 10000;
const int INF = 0x3f3f3f3f;


//切记初始化还有判自环


int V; //n个点
int map[MAXV + 7][MAXV + 7];
bool visited[MAXV + 7];
bool flag[MAXV + 7];//缩点标记为true,否则仍然存在
int pre[MAXV + 7];//点i的父节点为pre[i]
 
//以root为根节点的最小树形图
int zhuliu(int root){
    int sum = 0;//最小树形图的权值
    int i, j, k;
    pre[root] = root;
    while(true){
        for(i = 1; i <= V; i++){//求最短弧集合E0
            if(flag[i] || i == root) continue;
            pre[i] = i;
            for(j = 1; j <= V; j++)
                if(!flag[j] && map[j][i] < map[pre[i]][i])
                    pre[i] = j;
            if(pre[i] == i) return -1;
        }
        for(i = 1; i <= V; i++){//检查E0
            if(flag[i] || i == root) continue;
            for(j = 1; j <= V; j++) visited[j] = false;
            visited[root] = true;
            j = i;//从当前点开始找环
            do{
                visited[j] = true;
                j = pre[j];
            }while(!visited[j]);
            if(j == root)continue;//没找到环
            i = j;//收缩G中的有向环
            do{//将整个环的取值保存,累计计入原图的最小树形图
                sum += map[pre[j]][j];
                j = pre[j];
            }while(j != i);
            j = i;
            do{//对于环上的点有关的边,修改其权值
                for(k = 1; k <= V; k++)
                    if(!flag[k] && map[k][j] < INF && k != pre[j])
                        map[k][j] -= map[pre[j]][j];
                j = pre[j];
            }while(j != i);
            for(j = 1; j <= V; j++){//缩点,将整个环缩成i号点,所有与环上的点有关的边转移到点i
                if(j == i) continue;
                for(k = pre[i]; k != i; k = pre[k]){
                    if(map[k][j] < map[i][j]) map[i][j] = map[k][j];
                    if(map[j][k] < map[j][i]) map[j][i] = map[j][k];
                }
            }
            for(j = pre[i]; j != i; j = pre[j]) flag[j] = true;//标记环上其他点为被缩掉
            break;//当前环缩点结束,形成新的图G',跳出继续求G'的最小树形图
        }
        if(i > V){//如果所有的点都被检查且没有环存在,现在的最短弧集合E0就是最小树形图.累计计入sum,算法结束
            for(i = 1; i <= V; i++)
                if(!flag[i] && i != root) sum += map[pre[i]][i];
            break;
        }
    }
    return sum;
}

int main() 
{
    
}

虽然不想承认,但是邻接矩阵存储真的clumsy,再写个结构体存储的。

const int inf = 2e9;
const int maxn = 105;
const int maxm = 100050;

int n, m;//顶点数,边数  
int in[maxn];//in[u]记录当前图中指向u结点的所有边权中最小的那条边权 
int pre[maxn];//pre[u]记录最小边权对应的父亲结点 
int used[maxn], id[maxn];//used是访问标记数组,id[u]是计算出u在下一次的新图中的编号 

struct Edge {
    int from, to;
    int dist;
    Edge(int f = 0, int t = 0, int d = 0) :from(f), to(t), dist(d) {}
}edges[maxm];//边集 

int direct_mst(int root, int V, int E) {//三个参数分别是根结点,顶点数量,边数量 
    int ans = 0;
    while (1) {
        //为每个非根结点选出最小入边 
        for (int i = 0; i < V; ++i) in[i] = inf;
        for (int i = 0; i < E; ++i) {
            int u = edges[i].from;
            int v = edges[i].to;
            if (in[v] > edges[i].dist && u != v) {
                in[v] = edges[i].dist;
                pre[v] = u;
            }
        }
        //判断连通性,如有不可达结点说明无解
        for (int i = 0; i < V; ++i) {
            if (i == root) continue;
            if (inf == in[i]) return -1;
        }

        //判断有向环是否存在,存在有向环就缩圈
        int cnt = 0;//生成新图的结点编号
        memset(id, -1, sizeof(id));//id[u]==-1表示结点u还不属于任何一个自环 
        memset(used, -1, sizeof(used));
        in[root] = 0;
        for (int i = 0; i < V; ++i) {
            ans += in[i];
            int v = i;
            while (used[v] != i && id[v] == -1 && v != root) {//每个结点不断向上寻找父亲结点,要么找到根结点,要么形成一个自环 
                used[v] = i;
                v = pre[v];
            }
            if (v != root && id[v] == -1) {//找到了自环,进行缩点,更新id数组 
                for (int u = pre[v]; u != v; u = pre[u]) id[u] = cnt;
                id[v] = cnt++;
            }
        }

        if (0 == 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 = edges[i].from;
            int v = edges[i].to;
            edges[i].from = id[u];
            edges[i].to = id[v];
            if (id[u] != id[v]) edges[i].dist -= in[v];
            //这里id[u] != id[v]说明edges[i]这条边原来不在有向环中,
            //如果这条边指向了有向环,那么它的边权就要减少in[v]等价于整个环的边权减去in[v]
            //而如果没有指向有向环,说明它与这个有向环毫无关系,那么在之前的寻找自环缩点过
            //程中已经把这条边的权值加上了,所以这里避免重复计算让这条边的权值减小in[v]变为0 
        }
        V = cnt;
        root = id[root];
    }
    return ans;
}

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值