1. 程式人生 > 實用技巧 >P1364 醫院設定 樹形DP + 帶權樹的重心

P1364 醫院設定 樹形DP + 帶權樹的重心

傳送門

題目描述

設有一棵二叉樹,如圖:

其中,圈中的數字表示結點中居民的人口。圈邊上數字表示結點編號,現在要求在某個結點上建立一個醫院,使所有居民所走的路程之和為最小,同時約定,相鄰接點之間的距離為 11。如上圖中,若醫院建在1 處,則距離和 =4+12+2\times20+2\times40=136=4+12+2×20+2×40=136;若醫院建在 33 處,則距離和 =4\times2+13+20+40=81=4×2+13+20+40=81。

輸入描述

第一行一個整數 nn,表示樹的結點數。

接下來的 nn 行每行描述了一個結點的狀況,包含三個整數 w, u, vw,u,v,其中 ww 為居民人口數,uu 為左連結(為 00 表示無連結),vv 為右連結(為 00 表示無連結)。

輸出描述

一個整數,表示最小距離和。

分析

這道題和P3478 [POI2008]STA-Station有點類似,大致思路都是從樹的深度下手,找出狀態轉移方程

首先這道題的資料比較有意思,比較直接的方法就是用Floyd求出兩點間的距離,然後列舉每一個點即可,時間複雜度為O(n
2
)
還有一種O(n)的做法就是先求出以1為根結點時的答案,然後不斷往下轉換根節點,由於子樹的深度 -1,所以對答案的影響是 - Size[j],上半部分的深度 +1,所以對答案的影響是 Size[1] - Size[j],所以狀態轉移方程為:f[j] = f[u] +Size[1] - Size[j] * 2;

程式碼

#include <iostream>
#include <cstring>
#include <cstdio>

using namespace std;

const int N = 110,M = 2 * N;
int h[N],ne[M],e[M],idx;
int d[N];
int w[N];
int Size[N];
int f[N];
int n;
int ans = 0x3f3f3f3f;

void add(int x,int y){
    ne[idx] = h[x],e[idx] = y,h[x] = idx++;
}

void dfs(int u,int fa){
    d[u] = d[fa] + 1;
    Size[u] = w[u];
    for(int i = h[u];~i;i = ne[i]){
        int j = e[i];
        if(j == fa) continue;
        dfs(j,u);
        Size[u] += Size[j];
    }
    f[1] += d[u] * w[u];
}

void dp(int u,int fa){
    for(int i = h[u];~i;i = ne[i]){
        int j = e[i];
        if(j == fa) continue;
        f[j] = f[u] + Size[1] - Size[j] * 2;
        dp(j,u);
    }
    ans = min(ans,f[u]);
}

int main(){
    scanf("%d",&n);
    memset(h,-1,sizeof h);
    for(int i = 1;i <= n;i++){
        int x,y;
        scanf("%d%d%d",&w[i],&x,&y);
        if(x) add(i,x),add(x,i);
        if(y) add(i,y),add(y,i);
    }
    d[0] = -1;
    dfs(1,0);
    dp(1,0);
    printf("%d\n",ans);
    return 0;
}