最小树形图(朱刘算法)

有向图,从某一点出发到其他所有点的最小权值总和。

过程:

  1. 选入边集,找到除rot节点外,每一个点的所有入边中权值最小的,用in[]记录这个最小权值,用pre[]记录该最小入边的前驱;(若图中存在孤立节点的话,不存在最小树形图)
  2. 找有向环,用id[]记录节点所属环的编号。
  3. 将有向环缩点,并更新与之有直连边的权值。
  4. 以环的数量为下一次查找的点数建新图,继续重复上述过程直到没有环或者判定出不存在最小树形图为止。
//记录点的最小入边权,这个入边的前驱,i属于的环的编号;
int in[maxn],pre[maxn],idx[maxn],vis[maxn];

//点编号0~totn-1,边编号0~totm-1;
int ZhuLiu(int root,int totn,int totm){
    int u,v,ans=0;
    while(1){
        for(int i=0;i<totn;++i) in[i]=inf;
        for(int i=0;i<totm;++i){
            Edge& e=edge[i];
            if(e.u!=e.v&&e.w<in[e.v]){
                in[e.v]=e.w;
                pre[e.v]=e.u;
            }
        }

        for(int i=0;i<totn;++i)
            if(in[i]==inf&&i!=root) return -1;//孤立点,无最小树形图;

        int tot=0;
        memset(vis,-1,sizeof(vis));
        memset(idx,-1,sizeof(idx));
        in[root]=0;
        for(int i=0;i<totn;++i){
            ans+=in[i];
            v=i;
            //找环;
            while(vis[v]!=i&&idx[v]==-1&&v!=root){
                vis[v]=i;
                v=pre[v];
            }

            //对环继续收缩;
            if(v!=root&&idx[v]==-1){
                for(u=pre[v];u!=v;u=pre[u]) idx[u]=tot;
                idx[v]=tot++;
            }
        }
        //找不到新的缩点;
        if(!tot) break;

        //建新图;
        for(int i=0;i<totn;++i)
            if(idx[i]==-1) idx[i]=tot++;
        for(int i=0;i<totm;++i){
            v=edge[i].v;
            edge[i].u=idx[edge[i].u];
            edge[i].v=idx[edge[i].v];
            if(edge[i].u!=edge[i].v)
                edge[i].w-=in[v];
        }

        totn=tot;

        //保持根不变;
        root=idx[root];
    }
    return ans;
}

一道例题:Ice_cream’s world II HDU - 2121

问存不存在一个点可以作为最小树形图的起始点。
那么最直观的想法肯定是枚举每个点作为起始点跑一次朱刘算法,但这么做会超时,那么我们可以建立一个虚拟节点,将所有节点与之相连,sum=边权为总边权和+1,ans=朱刘算法。
如果 a n s &gt; = 2 ∗ s u m ans&gt;=2*sum ans>=2sum,说明起码需要两条边从虚拟节点出发,才能走遍所有的图,否则就得到了最小树形图。
该题还需要找到这个点是谁,并且如果有多个答案的话选择最小编号的节点,那么就在跑图的过程中记录一下哪个点的前驱节点是虚拟节点的话他就可能成为答案。

#include<bits/stdc++.h>
using namespace std;


const int maxn=1e3+7;
const int maxm=2e4+7;
const int inf=0x3f3f3f3f;

struct Edge{
    int u,v,w,next;
}edge[maxm];

int head[maxn],top;

void init(){
    memset(head,-1,sizeof(head));
    top=0;
}

void add(int u,int v,int w){
    edge[top].u=u;
    edge[top].v=v;
    edge[top].w=w;
    edge[top].next=head[u];
    head[u]=top++;
}

//记录点的最小入边,这个入边的前驱,i属于的环的编号;
int in[maxn],pre[maxn],idx[maxn],rt,vis[maxn];

//点编号0~totn-1,边编号0~totm-1;
int ZhuLiu(int root,int totn,int totm){
    int u,v,ans=0;
    while(1){
        for(int i=0;i<totn;++i) in[i]=inf;
        for(int i=0;i<totm;++i){
            Edge& e=edge[i];
            if(e.u!=e.v&&e.w<in[e.v]){
                in[e.v]=e.w;
                pre[e.v]=e.u;
                if(e.u==root) rt=i;
            }
        }

        for(int i=0;i<totn;++i)
            if(in[i]==inf&&i!=root) return -1;//孤立点,无最小树形图;

        int tot=0;
        memset(vis,-1,sizeof(vis));
        memset(idx,-1,sizeof(idx));
        in[root]=0;
        for(int i=0;i<totn;++i){
            ans+=in[i];
            v=i;
            //找环;
            while(vis[v]!=i&&idx[v]==-1&&v!=root){
                vis[v]=i;
                v=pre[v];
            }

            //对环继续收缩;
            if(v!=root&&idx[v]==-1){
                for(u=pre[v];u!=v;u=pre[u]) idx[u]=tot;
                idx[v]=tot++;
            }
        }
        //找不到新的缩点;
        if(!tot) break;

        //建新图;
        for(int i=0;i<totn;++i)
            if(idx[i]==-1) idx[i]=tot++;
        for(int i=0;i<totm;++i){
            v=edge[i].v;
            edge[i].u=idx[edge[i].u];
            edge[i].v=idx[edge[i].v];
            if(edge[i].u!=edge[i].v)
                edge[i].w-=in[v];
        }

        totn=tot;

        //保持根不变;
        root=idx[root];
    }
    return ans;
}

int main(){
    int n,m,u,v,w,sum;
    while(scanf("%d%d",&n,&m)!=EOF){
        sum=0;
        init();
        for(int i=0;i<m;++i){
            scanf("%d%d%d",&u,&v,&w);
            add(u,v,w);
            sum+=w;
        }
        ++sum;

        for(int i=0;i<n;++i) add(n,i,sum);
        int ans=ZhuLiu(n,n+1,top);
        if(ans==-1||ans>=2*sum) printf("impossible\n");
        else printf("%d %d\n",ans-sum,rt-m);
        需要注意这里不能写成edge[rt].v,因为在跑图的过程中这个点所在的环可能缩点了,
        那么这个点就会改变,所以只能是最开始他代表的意义。
        printf("\n");
    }

    return 0;
}

### 有向图最小生成树的Tarjan算法 #### 定义与概念 在讨论有向图中的最小生成树之前,需先澄清一些基本概念。对于有向图而言,“最小生成树”的表述并不完全适用;更确切地说,应探讨的是**最小树形图**的概念。最小树形图是指以某个特定顶作为根节的一棵有向树,该树覆盖除根外的所有其他顶,并使得这些边上的和达到最小[^3]。 #### Tarjan算法概述 尽管Tarjan算法主要用于寻找有向图中的强连通分量,但在某些情况下也可以被扩展用于构建最小树形图。然而需要注意的是,直接利用Tarjan算法来计算最小树形图并不是最常用的方法。通常提到的求解最小树形图的经典方法是朱刘算法。不过,在处理复杂网络结构时,Tarjan算法可以作为一种辅助工具帮助识别潜在的关键路径或子图特性。 #### 应用实例分析 考虑到Tarjan算法本身不是专门用来解决最小树形图问题的技术手段,因此这里提供一个基于Tarjan算法框架下间接支持最小树形图查找的例子: 假设在一个大型社交平台中存在大量用户之间的关注关系形成了一张复杂的有向加图(每条边上带有表示互动频率或其他度量标准的数),此时想要找出某位明星用户的影响力传播链路——即从这位明星出发能够影响到尽可能多粉丝群体的同时保持整体关联强度最大化的方案。这时就可以借助Tarjan算法快速定位出由该名星及其直系/间接下属构成的一个个独立社区(即强联通分支),再进一步运用诸如动态规划等策略评估并挑选最优组合方式达成目标。 ```python def tarjan_scc(graph): index_counter = [0] stack = [] lowlinks = {} index = {} result = [] def strongconnect(node): # Set the depth index for this node to be the next available counter. index[node] = index_counter[0] lowlinks[node] = index_counter[0] index_counter[0] += 1 stack.append(node) try: successors = graph[node] except KeyError: successors = [] for successor in successors: if successor not in lowlinks: # Successor has not yet been visited; recurse on it strongconnect(successor) lowlinks[node] = min(lowlinks[node], lowlinks[successor]) elif successor in stack: # The successor is already in the stack and hence part of current SCC lowlinks[node] = min(lowlinks[node], index[successor]) # If `node` is a root node, pop the stack and generate an SCC if lowlinks[node] == index[node]: connected_component = [] while True: successor = stack.pop() connected_component.append(successor) if successor == node: break component = tuple(connected_component) result.append(component) for node in graph.keys(): if node not in lowlinks: strongconnect(node) return result ``` 此代码片段展示了如何使用Tarjan算法检测给定有向图内的所有强连通分量。虽然这段代码并未直接涉及最小树形图的具体实现细节,但它提供了关于如何组织数据以及遍历过程的基础思路,这对于后续开发针对最小树形图优化的功能模块非常有用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值