hdu 4009 最小樹形圖(O(m))

題目鏈接

基本算法


最小樹形圖基於貪心和縮點的思想。
1. 求最短弧集合E0
從所有以vi 爲終點的弧中取一條最短的,若對於vi ,沒有入邊,則不存在最小樹形圖,算法結束;如果能取,則得到有n個點和n-1條邊組成的圖G 的一個子圖G ,該子圖的權值一定是的最小的,但是不一定是一棵樹。
2. 檢查E0
E0 沒有有向環且不含收縮點,則計算結束,E0 就是以Gv0 爲根的最小樹形圖。若E0 沒有有向環,但含收縮點,則轉步驟(4),若E0 含有有向環C,則轉入步驟(3)。
3. 收縮G 的C收縮成點u,對於圖G 中兩端都屬於C的邊的都被收縮掉了,其它弧仍保留,得到一個新的圖G1 ,G1 中以收縮點爲終點的弧的長度都要變化,變化的規律是:設點v 在環C中,且環中指向v 的邊長爲w,點v 不在環C中,則對於每一條邊<v,v> ,在G1 中有邊<v,u> 與其對應,且權值W(G1)(<v,u>)=WG(<v,v>)w 。對於圖G 中以環C爲C的起點的邊<v,v> , 在圖中G1 有邊<u,v> ,則W(G1)(<u,v>)=WG(<v,v>)
4. 展開縮點。

題意


每個家庭挖水井需要花費z*X, 如果挖水渠就是地勢高的家庭到地勢低的曼哈頓距離乘以Y,若是低到高在加Z。最後求最小花費。


解析


建圖的最難處理的就是那個,自己挖水井。建立一個源點,和每個點建邊,權值爲在z*X,剩下的就根據關係建邊即可。最後求有向圖的最小樹形圖即可。


代碼

#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <algorithm>
#include <vector>
#include <cmath>
using namespace std;
const int INF=0x3f3f3f3f;
const int maxn = 1000+100;
int dfn[maxn], low[maxn];
int st[maxn];
int stk = 0;
struct node {
    int u, v, w;
    node ()
    {}
    node (int _u, int _v, int _w) {
        u = _u;
        v = _v;
        w = _w;
    }
}e[maxn*maxn];
int dep = 0, bcc_cnt;
int in[maxn], vis[maxn], id[maxn], pre[maxn];
vector<int>g[maxn];
struct point {
    int x, y, z;
    point(){}
}p[maxn];

int dis(point a, point b) {
    return abs(a.x-b.x)+abs(a.y-b.y)+abs(a.z-b.z);
} 

int Directed_MST(int root, int n, int m) {
    int ret = 0;
    while (1) {
        //第一步:找到入邊最小邊
        for (int i=0; i<n; i++)
            in[i] = INF;
        for (int i=0; i<m; i++) {
            int u = e[i].u, v = e[i].v;
            if (e[i].w < in[v] && u != v) {
                in[v] = e[i].w;
                pre[v] = u;
            }
        }

        for (int i=0; i<n; i++) { //沒有入邊,就不會產生最小樹形圖
            if (i == root)
                continue;
            if (in[i] == INF)
                return -1;
        }
        int cntnode = 0;
        memset(id, -1, sizeof(id));
        memset(vis, -1, sizeof(vis));
        // 第二步:找環
        in[root] = 0;
        for (int i=0; i<n; i++) {
            ret += in[i];
            int v = i;
            while (vis[v] != i && id[v] == -1 && v != root) {
                vis[v] = i;
                v = pre[v];
            }
            if (v != root && id[v] == -1) {
                for (int u=pre[v]; u!=v; u=pre[u])
                    id[u] = cntnode;
                id[v] = cntnode++;
            }
        }
        if (cntnode  == 0)
            break;

        for (int i=0; i<n; i++) {
            if (id[i] == -1)
                id[i] = cntnode++;
        }

        //第三步:縮點、重新標記
        for (int i=0; i<m; i++) {
            int 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];
        }
        n = cntnode;
        root = id[root];
    }
    return ret;
}

int main() {
    int n, X, Y, Z;
    while (~scanf("%d%d%d%d", &n, &X, &Y, &Z) && n) {
        int S = 0;
        int m = 0;
        for (int i=1; i<=n; i++) {
            scanf("%d%d%d", &p[i].x, &p[i].y, &p[i].z);
            e[m++] = node(S, i, p[i].z*X);
            g[S].push_back(i);
        }
        for (int i=1; i<=n; i++) {
            int k;
            scanf("%d", &k);
            int u = i;
            while (k--) {
                int v;
                scanf("%d", &v);
                if (v == i)
                    continue;
                int w = dis(p[u], p[v])*Y;
                if (p[u].z < p[v].z)
                    w += Z;
                e[m++] = node (u, v, w);
            }
        }
        int ans = Directed_MST(0, n+1, m);
        printf("%d\n", ans);
    }
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章