avatar
fireworks99
keep hungry keep foolish

HDU 2121 Ice-cream's world II(Minimum tree-shape graph)

Description

N个点(0~N-1)M条有向边,不定根(任意一点可选做根节点)求最小树形图

Anlyze

增加一个超级节点”伪根“,连向每一个点。

关于这些边的权值:

①最初想的是像最短路题目似的,将超点连向各点的边的权值设为0,最后得出的最短路长度就直接是答案。后来想想不对,朱刘算法第一步找“最小入边集”,那样选出来的最小入边集就是这新增的N条边了。

②所以为了不影响寻找正常的最小入边集,这新增的N条边的权值应该比最小入边集里所有边的长度都要大,假设最小入边集中最长边长度为X,那这N条新边权值要设为 >= X的一个数。

③那样做还要先求一遍最小入边集,干脆放大一下,找原图所有边中的最长边,设为Y,那么这N条新边的边权要 >= Y;

④还有个问题,我在求最小树形图时,用了两条“新边”(理论上应该只用一条:u=伪根,v=真根 の那条边),后期无法鉴别。而倘若我直接把“新边”边权设为sum(原图所有边的边权和),最后答案小于2倍的sum才算真正找到了最小树形图。

所以说N条新边的边权应 >= sum,最后记得从答案里减掉一个新边边权。

关于寻找“真根”:

理论上来说,在选最小入边集时,我们记录了每个点的前驱,if(u == 伪根) 则v == 真根。但还要“缩点”,“缩点”会使所有点的下标屡次改变,这时记录的点就无意义了。

但是,我们是按顺序添加新边的,比如某条新边下标为m + x,它从伪根指向节点x,那么即使后来缩点了,那唯一一条连接伪根与真根的边,它的u、v都变了,但下边没变,那么那条边依然是独一无二的(它曾指向“真根”,证据就是它的下标 - m == 真根)。(事实上每条边都是独一无二的)

5 5
4 3 1
3 1 2
1 4 3
1 2 1
2 0 1

///1.Set of in
for(int i = 0; i < n; ++i)
    in[i] = -1;
for(int i = 1; i <= m; ++i)
    if(e[i].u != e[i].v && (e[i].w < in[ e[i].v ] || in[ e[i].v ] < 0))
    {
        in[ e[i].v ] = e[i].w, pre[ e[i].v ] = e[i].u;
        if(e[i].u == root)
        {
            cout << i << ' ' << e[i].u << ' ' << e[i].v << '\n';
            start = i;
        }
    }

理解第一个if的第二个条件,在缩点后的意义:选取环上最长边的终点v作为此环的“进入点”

Code

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1005;
const int INF = 0x3f3f3f3f;

int n, m, id[N], pre[N], vis[N], in[N], start;

struct edge
{
    int u, v, w;
}e[10 * N + N];///Pay attention to the number of edges!

int zhuliu(int root)
{
    int u, v, res = 0;
    while(true)
    {
        ///1.Set of in
        for(int i = 0; i < n; ++i)
            in[i] = -1;
        for(int i = 1; i <= m; ++i)
            if(e[i].u != e[i].v && (e[i].w < in[ e[i].v ] || in[ e[i].v ] < 0))
            {
                in[ e[i].v ] = e[i].w, pre[ e[i].v ] = e[i].u;
                if(e[i].u == root)
                    start = i;
            }
        for(int i = 0; i < n; ++i)
            if(i != root && in[i] < 0)
                return -1;

        ///2.Directed circle
        int cnt = 0;
        memset(id, -1, sizeof(id));
        memset(vis,-1, sizeof(vis));
        in[root] = 0;
        for(int i = 0; i < n; ++i)
        {
            res += in[i];
            v = i;
            while(id[v] == -1 && v != root && vis[v] != i)
                vis[v] = i, v = pre[v];
            if(id[v] == -1 && v != root)///   vis[v] == i
            {
                for(u = pre[v]; u != v; u = pre[u])
                    id[u] = cnt;
                id[v] = cnt++;
            }
        }

        ///3.No circle -> Over
        if(cnt == 0)
            break;

        ///4.Process other nodes ,and then "shrink".
        for(int i = 0; i < n; ++i)
            if(id[i] == -1)
                id[i] = cnt++;
        for(int i = 1; i <= m; ++i)
        {
            v = e[i].v;
            e[i].u = id[ e[i].u ];
            e[i].v = id[ e[i].v ];
            if(e[i].u != e[i].v)
                e[i].w -= in[v];///This v is the original v!
        }
        n = cnt;
        root = id[root];
    }
    return res;
}

int main()
{
    while(~scanf("%d %d", &n, &m))
    {
        int u, v, w, tn = n, tm = m, sum = 0;
        for(int i = 1; i <= m; ++i)
        {
            scanf("%d %d %d", &u, &v, &w);
            e[i].u = u, e[i].v = v, e[i].w = w;
            sum += w;
        }
        for(int i = 0; i < n; ++i)
        {
            e[m + i + 1].u = n;
            e[m + i + 1].v = i;
            e[m + i + 1].w = sum + 1;
        }
        m += n, n++;///Pay attention to the order!
        int ans = zhuliu(tn);
        if(ans < 0 || ans >= 2 * (sum + 1))
            cout << "impossible" << '\n' << '\n' ;
        else
            printf("%d %d\n\n", ans - (sum + 1), start - tm - 1);
    }
    return 0;
}
Site by Baole Zhao | Powered by Hexo | theme PreciousJoy