[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\)
輸出格式
一個整數,表示最小距離和。
輸入輸出樣例
輸入樣例 #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;
}