最小树形图【有向图的最小生成树】

版权声明:https://blog.csdn.net/qq_41730082 https://blog.csdn.net/qq_41730082/article/details/85338921

  做了POJ的一道题,总是WA,不知道为什么,后来去看了,才知道,原来有向图的最小生成树与无向图不一样,它得是从某个点出发能遍历到其他所有点的才行,以此为条件,我们学习到了最小树形图。

  与很多其他人的讲解不一样吧,我把我看了博客后自己的思路分享出来,关于什么是最小树形图。

  我们知道的既然要建立最小树形图,就要理解什么是最小树形图,(概念好难懂啊,还是自己写的清楚明白),我们从某一点出发(或者是固定点),能通过它跑完所有点的最小花费,就是最小树形图了。

  那么,怎么去搭建最小树形图?会有人看到关于最小弧这样的讲法,诶!确实是这样的,但是你们理解什么是最小弧吗?我们想构建一颗最小生成树的时候,就是找到这样的最优解的边逐条放进去的(Kruskal算法思想),但是这也是一样的,我们找到所有非根节点的最小入边,先把这样的所有入边给加进来,那么得到的一幅图,可能还真是不完全,要是遇到了个,岂不是有趣,或者呢,压根就走不完!不就GG?所以,就这样就被我们想出了两个需要判断的条件了。

  把所有的最小入边先加起来,我们得到了一个花里胡哨的图,可能它就是多个环的集合,也许恰好是答案,这都是不确定的,若是恰好是已经没有环了,那么这就是答案了;反之,就是有环,那么,我们得到的边,就不一定是所有的点构在一起的图(也许会成为森林这样的情况),那么,把环搜索起来吧,我们把一个环搜索成一个点,然后对于它(新点——即所谓的缩点)的入边,我们建立新边的时候,需要考虑到我们得删除原来在这幅图里的改点的入边(就是我们已经存入了这个原节点的入边了,但是,它却构成了环,说明不是我想要的解),所以,新边的权值就是原权值减去终点节点的最小入边。然后,节点数就会变少了,我们就可以继续在这样子优化下去了。直到上面说到的没有再构成环的时候,就说明是解了。


然后呢,我这挂一道Ice_cream’s world II HDU--2121的解题报告,带上完整的注释说明:

#include <iostream>
#include <cstdio>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <limits>
#include <vector>
#include <stack>
#include <queue>
#include <set>
#include <map>
#define lowbit(x) ( x&(-x) )
#define pi 3.141592653589793
#define e 2.718281828459045
#define INF 0x3f3f3f3f
using namespace std;
typedef unsigned long long ull;
typedef long long ll;
const int maxN = 1005;
int N, M;
ll sum;
struct Eddge    //存边
{
    int u, v;
    ll val;
    Eddge(int a=0, int b=0, ll c=0):u(a), v(b), val(c) {}
}edge[maxN*maxN];
int pre[maxN], id[maxN], vis[maxN], pos;
ll in[maxN];    //最小入边权,pre[]为其前面的点(该边的起点)
ll Dir_MST(int root, int V, int E)  //root是此时的根节点,我们最初的时候将0(万能节点作为根节点进入),V是点的个数(包括之后要收缩之后点的剩余个数),E是边的条数(不会改变)
{
    ll ans = 0;
    while(true)     //如果还是可行的话
    {
        for(int i=0; i<V; i++) in[i] = INF; //给予每个点进行初始化
        /* (1)、最短弧集合E0 */
        for(int i=1; i<=E; i++)     //通过这么多条单向边,确定的是每个点的指向边的最小权值
        {
            int u = edge[i].u, v = edge[i].v;
            if(edge[i].val < in[v] && u!=v)     //顶点v有更小的入边,记录下来    更新操作,u!=v是为了确保缩点之后,我们的环将会变成点的形式
            {
                pre[v] = u;     //节点u指向v
                in[v] = edge[i].val;    //最小入边
                if(u == root) pos = i;  //这个点就是实际的起点
            }
        }
        /* (2)、检查E0 */
        for(int i=0; i<V; i++)     //判断是否存在最小树形图
        {
            if(i == root) continue;     //是根节点,不管
            if(in[i] == INF) return -1;     //除了根节点以外,有点没有入边,则根本无法抵达它,说明是独立的点,一定不能构成树形图
        }
        /* (3)、收缩图中的有向环 */
        int cnt = 0;    //接下来要去求环,用以记录环的个数  找环开始!
        memset(id, -1, sizeof(id));
        memset(vis, -1, sizeof(vis));
        in[root] = 0;
        for(int i=0; i<V; i++)     //标记每个环
        {
            ans += in[i];   //加入每个点的入边(既然是最小入边,所以肯定符合最小树形图的思想)
            int v = i;  //v一开始先从第i个节点进去
            while(vis[v] != i && id[v] == -1 && v != root)  //退出的条件有“形成了一个环,即vis回归”、“到了一个环,此时就不要管了,因为那边已经建好环了”、“到了根节点,就是条链,不用管了”
            {
                vis[v] = i;
                v = pre[v];
            }
            if(v != root && id[v] == -1)    //如果v是root就说明是返回到了根节点,是条链,没环;又或者,它已经是进入了对应环的编号了,不需要再跑一趟了
            {
                for(int u=pre[v]; u!=v; u=pre[u])   //跑这一圈的环
                {
                    id[u] = cnt;    //标记点u是第几个环
                }
                id[v] = cnt++;  //如果再遇到,就是下个点了
            }
        }
        if(cnt == 0) return ans;    //无环的情况,就说明已经取到了最优解,直接返回,或者说是环已经收缩到没有环的情况了
        for(int i=0; i<V; i++) if(id[i] == -1) id[i] = cnt++;   //这些点是环外的点,是链上的点,单独再给他们赋值
        for(int i=1; i<=E; i++)     //准备开始建立新图  缩点,重新标记
        {
            int u = edge[i].u, v = edge[i].v;
            edge[i].u = id[u];  edge[i].v = id[v];  //建立新图,以新的点进入
            if(id[u] != id[v]) edge[i].val -= in[v];    //为了不改变原来的式子,使得展开后还是原来的式子
        }
        V = cnt;    //之后的点的数目
        root = id[root];    //新的根节点的序号,因为id[]的改变,所以根节点的序号也改变了
    }
    return ans;
}
int main()
{
    while(scanf("%d%d", &N, &M)!=EOF)
    {
        sum = 0;
        for(int i=1; i<=M; i++)
        {
            scanf("%d%d%lld", &edge[i].u, &edge[i].v, &edge[i].val);
            edge[i].u++;   edge[i].v++;   //把‘0’号节点空出来,用以做万能节点,留作之后用
            sum += edge[i].val;
        }
        sum++;  //一定要把sum给扩大,这就意味着,除去万能节点以外的点锁构成的图的权值和得在(sum-1)之内(包含)
        for(int i=M+1; i<=M+N; i++)     //这就是万能节点了,就是从0这号万能节点有通往所有其他节点的路,而我们最后的最小树形图就是从这个万能节点出发所能到达的整幅图
        {
            edge[i] = Eddge(0, i-M, sum);   //对于所有的N个其他节点都要建有向边
        }       //此时N+1为总的节点数目,M+N为总的边数
        ll ans = Dir_MST(0, N + 1, M+N);    //ans代表以超级节点0为根的最小树形图的总权值
        if(ans == -1 || ans - sum >= sum) printf("impossible\n");   //从万能节点的出度只能是1,所以最后的和必须是小于sum的,而万能节点的出度就由“ans - sum >= sum”保证
        else printf("%lld %d\n", ans - sum, pos - M - 1);   //pos-M得到的是1~N的情况,所以“-1”的目的就在于这里
        printf("\n");
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_41730082/article/details/85338921
今日推荐