P4716 【模板】最小树形图

  一道模版题(就是这个算法有点偏……)

  这道题就是在一个有向图中,求出一个定根的有根树,使其边权之和最小,其实就是有向图的最小生成树。

  其实挺简单的……没我想象的那么高深。就是在改边权的地方有点不好理解,正确性可以用数学归纳法证明。

  一次次缩点直到这个图不再有环为止。

  代码:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
#include<climits>
using namespace std;
#define maxn 20000
#define inf 999999999
struct node
{
    int from,to,w;
} edge[maxn];
int in[maxn],pre[maxn],id[maxn];
int vis[maxn];
int n,m,root,cnt;
void add(int a,int b,int w)
{
    edge[++cnt].to=b;
    edge[cnt].from=a;
    edge[cnt].w=w;
}
int zhuliu()
{
    int ans=0;
    while(1)
    {
        for(int i=1; i<=n; i++)
            in[i]=inf;
        for(int i=1; i<=m; i++)
        {
            int u=edge[i].from;
            int v=edge[i].to;
            if(u!=v&&edge[i].w<in[v])
            {
                in[v]=edge[i].w;
                pre[v]=u;
            }
        }
        for(int i=1; i<=n; i++)
            if(i!=root&&in[i]==inf) return -1;
        int num=0;
        memset(vis,0,sizeof(vis));
        memset(id,0,sizeof(id));
        for(int i=1;i<=n;i++)
        {
            if(i==root) continue;
            ans+=in[i];
            int v=i;
            while(vis[v]!=i&&id[v]==0&&v!=root)
            {
                vis[v]=i;
                v=pre[v];
            }
            if(v!=root&&id[v]==0)
            {
                id[v]=++num;
                for(int j=pre[v];j!=v;j=pre[j])
                    id[j]=num;
            } 
        }
        if(!num) break;
        for(int i=1;i<=n;i++)
            if(!id[i]) id[i]=++num;
        for(int i=1;i<=m;i++)
        {
            int u=edge[i].from;
            int v=edge[i].to;
            edge[i].from=id[u];
            edge[i].to=id[v];
            if(id[u]!=id[v]) edge[i].w-=in[v];
        }
        root=id[root];
        n=num;
    }
    return ans;
}
int main()
{
    scanf("%d%d%d",&n,&m,&root);
    for(int i=1; i<=m; i++)
    {
        int a,b,w;
        scanf("%d%d%d",&a,&b,&w);
        add(a,b,w);
    }
    printf("%d
",zhuliu());
    return 0;
}

   补充:

  有几个地方很重要不可以丢掉,比如找环的时候判断id和根节点。判断id是因为这条边可能会连到一个环里,而这个环已经被染色,不能再染一遍。判断根是因为我们认为根节点没有入边,因为根节点是被指定的树根。并且不用担心从不属于这个环的点连到环里使解错误,因为就算回到环里,也不会回到这个点而是会会到与这个点直接相连的那个环里的点,从这个点反而把环处理了一遍。而这个帮别人忙的点,我们一会儿整体扫一遍的时候会发现到,将被视为单点一个环。

原文地址:https://www.cnblogs.com/popo-black-cat/p/10952818.html