1. 程式人生 > 實用技巧 >[luogu p1364] 醫院設定 & 找樹的重心 學習筆記

[luogu p1364] 醫院設定 & 找樹的重心 學習筆記

傳送門

醫院設定

題目描述

設有一棵二叉樹,如圖:

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

輸入輸出格式

輸入格式

第一行一個整數 \(n\),表示樹的結點數。

接下來的 \(n\) 行每行描述了一個結點的狀況,包含三個整數 \(w, u, v\),其中 \(w\) 為居民人口數,\(u\)

為左連結(為 \(0\) 表示無連結),\(v\) 為右連結(為 \(0\) 表示無連結)。

輸出格式

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

輸入輸出樣例

輸入樣例 #1

5
13 2 3
4 0 0
12 4 5
20 0 0
40 0 0

輸出樣例 #1

81

說明

資料規模與約定

對於 \(100\%\) 的資料,保證 \(1 \leq n \leq 100\)\(0 \leq u, v \leq n\)\(1 \leq w \leq 10^5\)

分析

此題\(n \leq 100\),所以可以用floyd直接水過。

但直接用floyd的話,這道題還有什麼挑戰性?

於是我仔細分析了一下這道題,這道題是求樹的重心。

求樹的重心有沒有什麼巧妙的辦法呢?

想了1145141919810年後,沒想出來。

於是我毫不猶豫打開了題解,於是就看到了題解區的第一篇題解,終於學會了求樹的重心。

看完\(\operatorname{O}(n)\)的做法後,我的腦海中只剩下了一個詞: Orz

現在我就來記錄一下自己學到的在樹中找重心的方法。

首先,鄰接表存圖,然後dfs,預處理出所有節點的大小。(大小定義為此點的權值+所有兒子的大小)

虛擬碼如下:

void dfs(int u, int father, int depth) {
    siz[u] = w[u];
    for (int i = head[u]; i; i = edge[i].nxt) 
        if (edge[i].v != father) {
            dfs(edge[i].v, u, depth + 1);
            siz[u] += siz[edge[i].v];
        }
    dp[1] += w[u] * depth;
}

此處:

  • w代表此點權值,也就是題目中的w
  • siz代表此點大小
  • dp代表以此點為根的總距離

然後就是dp環節。

首先既然是dp,那麼狀態轉移方程是什麼呢?

先說狀態轉移方程:

\[dp_v = dp_u - siz_v + siz_1 - siz_v \]

為什麼?

首先先從變化的角度來看這個問題。

重心從父節點\(u\)轉到子節點\(v\),顯然,\(v\)的子樹中所有的單位權值(注意不是所有的子節點哦。子節點是一個地方,在此題代表社群。單位權值在此題就代表人)到重心(此題代表醫院)的距離都會少\(1\)。這點應該不難理解吧?這樣總距離就會少\(siz_v\)

但是這樣的代價是什麼呢?不是\(v\)的子樹中單位權值的,到重心的距離就會多\(1\)。這樣總距離就會增加\(siz_1 - siz_v\)。於是狀態轉移方程得證。

而答案,就是這些dp值中最小的那個。

dp環節虛擬碼:

void work(int u, int father) {
    for (int i = head[u]; i; i = edge[i].nxt)
        if (edge[i].v != father) {
            dp[edge[i].v] = dp[u] - siz[edge[i].v] * 2 + siz[1];
            work(edge[i].v, u);
        }
    ans = ans < dp[u] ? ans : dp[u];
}

兩個重要的核心說完了,其他都是細枝末節了,所以其實此處不上程式碼你應該也能自己寫出來了。

好,上程式碼。

dbxxx,你不是說此處不上程式碼,別人也能寫出來嗎?那你為啥還上?

你咋那麼多事,別管了,上就完事了

程式碼

/*
 * @Author: crab-in-the-northeast 
 * @Date: 2020-08-01 22:01:07 
 * @Last Modified by: crab-in-the-northeast
 * @Last Modified time: 2020-08-02 16:58:35
 */
//找樹的重心,學習於P1364第一篇題解。
#include <iostream>
#include <cstdio>
#include <climits>

const int maxn = 105;

struct Edge {
    int v, nxt;
}edge[maxn << 1];

int w[maxn];
int head[maxn], idx, n, siz[maxn];
int ans = INT_MAX >> 2, dp[maxn];

inline void addEdge(int u, int v) {
    edge[++idx].v = v;
    edge[idx].nxt = head[u];
    head[u] = idx;
}

void dfs(int u, int father, int depth) {
    siz[u] = w[u];
    for (int i = head[u]; i; i = edge[i].nxt) 
        if (edge[i].v != father) {
            dfs(edge[i].v, u, depth + 1);
            siz[u] += siz[edge[i].v];
        }
    dp[1] += w[u] * depth;
}

void work(int u, int father) {
    for (int i = head[u]; i; i = edge[i].nxt)
        if (edge[i].v != father) {
            dp[edge[i].v] = dp[u] - siz[edge[i].v] * 2 + siz[1];
            work(edge[i].v, u);
        }
    ans = ans < dp[u] ? ans : dp[u];
}

int main(){
    std :: scanf("%d", &n);
    for (int i = 1; i <= n; ++i) {
        std :: scanf("%d", &w[i]);
        int a, b;
        std :: scanf("%d%d", &a, &b);
        if (a) {
            addEdge(a, i);
            addEdge(i, a);
        }
        if (b) {
            addEdge(b, i);
            addEdge(i, b);
        }
    }
    dfs(1, 0, 0);
    work(1, 0);
    printf("%d", ans);
    return 0;
}

評測記錄

評測記錄