1. 程式人生 > 其它 >洛谷P1364醫院設定(樹形DP)

洛谷P1364醫院設定(樹形DP)

醫院設定

  本題給我們一棵樹還有所有點之間的關係,要我們找到醫院設在什麼位置的時候,在所有節點上的人到醫院所有走的距離和最小。要求的是所有點到某一個節點的距離和最小,我們可以想到樹的重心
  樹的重心的定義是樹若以某點為根,使得該樹最大子樹的結點數最小,那麼這個點則為該樹的重心,一棵樹可能有多個重心。它的性質就是:書上所有的點到樹的重心的距離是最小的。
  根據這一條性質,我們就不難想到可以先求出現在這一棵樹的重心在哪裡,從而就得到了以每一個節點為根的子樹的大小,這樣我們只要把每一個節點作為醫院所在位置時候的距離和都表示出來,從而在這些距離和中求出最小值就是我們想要的答案。
  在求重心的過程中,我們要把所有的點遍歷一遍,由於這是一棵無根樹,所以我們可以指定任意一個節點作為這一棵樹的根,這裡我們指定\(1\)

作為這一棵樹的根。在遍歷的同時,我們還要維護以當前點\(u\)為根的子樹的大小,我們用\(sz[u]\)來表示。距離和用\(dp\)陣列來儲存。

void dfs1(int u, int fa, int dep) { // u 表示的是根節點,fa 表示的是父節點,dep表示的是u節點到樹根的距離
    sz[u] = w[u];
    for (int i = h[u]; ~i; i = ne[i]) {
        int ver = e[i];
        if (ver == fa) continue;
        dfs1(ver, u, dep + 1);
        sz[u] += sz[ver];
    }
    dp[1] += w[u] * dep; 
}

  求出樹的重心之後,我們要考慮狀態轉移的過程。因為這一棵樹是帶有自身權值的,所以不一定是將醫院設在重心的位置對最後的答案最優,所以我們還是要再把所有的點遍歷一遍,假設當前是在節點\(u\),下一步是轉移到\(v\),那麼轉移方程就是\(dp[v] = dp[u] - sz[v] + sz[1] - sz[v]\),\(dp[u] - sz[v]\)表示的是原本\(v\)子樹所有的點要到\(u\),但是現在只需要到\(v\)了,每一個節點就少走了一步,所以轉移過程中距離和\(dp[v]\)先要在原本\(dp[u]\)的基礎上減去\(sz[v] * 1\),另外\(sz[1]-sz[v]\)表示的是除了\(v\)

子樹的節點以外的所有節點,它們在轉移的過程中多走了\(u->v\)這一步,所有距離和要加上\(sz[1]-sz[v]\)這一部分

void dfs2(int u, int fa) {
    for (int i = h[u]; ~i; i = ne[i]) {
        int ver = e[i];
        if (ver == fa) continue;
        dp[ver] = dp[u] + sz[1] - 2ll * sz[ver]; // sz[1]表示的整棵樹的大小,在前一次遍歷中將1設為了根節點
        dfs2(ver, u);
    }
    ans = std::min(ans, dp[u]);
}

那麼最後就只需要求出最小值即可

void solve()
{
    ans *= ans;
    int n; scanf("%d", &n);
    mem(h,-1);
    rep(i,1,n + 1) {
        scanf("%d", w + i);
        int a, b;
        scanf("%d%d", &a, &b);
        if (a) add(i, a), add(a, i);
        if (b) add(i, b), add(b, i);
    }

    dfs1(1, -1, 0);
    dfs2(1, -1);
    printf("%lld\n", ans);
}