例题
本题虽为二叉树,但可以转为图考虑
P1364 医院设置 - 洛谷
B站好视频
图-最短路径-Floyd(弗洛伊德)算法
核心代码
1 2 3 4 5 6 7 8 9 10
| // Floyd 算法求全源最短路 for (int k = 1; k <= n; k++) { for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { if (dist + dist < dist) { dist = dist + dist; } } } }
|
理解:
k为中转站,i与j为终点与起点,比较i->j与i->k->j的大小,而k(可以理解为i’与j’)也是不断更新的,所以最终就是最小的
缺点:
其时间复杂度为$O(n^3)$,仅可处理$N \le 500$的情况
DFS算法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| #include <iostream> #include <vector> #include <algorithm>
using namespace std;
int n; int weight[105]; vector<int> adj[105]; long long total_dist = 0;
void dfs(int u, int fa, int d) { total_dist += (long long)weight[u] * d;
for (int v : adj[u]) { if (v != fa) { dfs(v, u, d + 1); } } }
int main() { cin >> n; for (int i = 1; i <= n; i++) { int w, l, r; cin >> w >> l >> r; weight[i] = w; if (l) { adj[i].push_back(l); adj[l].push_back(i); } if (r) { adj[i].push_back(r); adj[r].push_back(i); } }
long long min_ans = -1;
for (int i = 1; i <= n; i++) { total_dist = 0; dfs(i, 0, 0);
if (min_ans == -1 || total_dist < min_ans) { min_ans = total_dist; } }
cout << min_ans << endl; return 0; }
|
核心代码
1 2 3 4 5 6 7 8 9 10
| void dfs(int u, int fa, int d) { total_dist += (long long)weight[u] * d;
for (int v : adj[u]) { if (v != fa) { dfs(v, u, d + 1); } } }
|
可能有一个问题是为什么DP仅用如此简单的判断方法v != fa,这是因为树是没有环的,所以只要不回头就可以。
其时间复杂度为$O(n^2)$,可处理$N \le 3000$的情况
DP算法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
| #include <iostream> #include <vector> #include <algorithm>
using namespace std;
const int MAXN = 105; struct Node { int w, l, r; } nodes[MAXN];
vector<int> adj[MAXN]; long long sz[MAXN]; long long ans[MAXN]; long long total_weight = 0;
void dfs1(int u, int fa, int depth) { sz[u] = nodes[u].w; ans[1] += (long long)nodes[u].w * depth;
for (int v : adj[u]) { if (v == fa) continue; dfs1(v, u, depth + 1); sz[u] += sz[v]; } }
void dfs2(int u, int fa) { for (int v : adj[u]) { if (v == fa) continue; ans[v] = ans[u] - sz[v] + (total_weight - sz[v]); dfs2(v, u); } }
int main() { int n; if (!(cin >> n)) return 0;
total_weight = 0; for (int i = 1; i <= n; i++) { cin >> nodes[i].w >> nodes[i].l >> nodes[i].r; total_weight += nodes[i].w; if (nodes[i].l) { adj[i].push_back(nodes[i].l); adj[nodes[i].l].push_back(i); } if (nodes[i].r) { adj[i].push_back(nodes[i].r); adj[nodes[i].r].push_back(i); } }
ans[1] = 0; dfs1(1, 0, 0);
dfs2(1, 0);
long long min_ans = -1; for (int i = 1; i <= n; i++) { if (min_ans == -1 || ans[i] < min_ans) { min_ans = ans[i]; } }
cout << min_ans << endl;
return 0; }
|
核心代码1
1 2 3 4 5 6 7 8 9 10 11 12
|
void dfs1(int u, int fa, int depth) { sz[u] = nodes[u].w; ans[1] += (long long)nodes[u].w * depth;
for (int v : adj[u]) { if (v == fa) continue; dfs1(v, u, depth + 1); sz[u] += sz[v]; } }
|
计算在节点u上的总代价
核心代码2
1 2 3 4 5 6 7 8 9 10
| void dfs2(int u, int fa) { for (int v : adj[u]) { if (v == fa) continue; ans[v] = ans[u] - sz[v] + (total_weight - sz[v]); dfs2(v, u); } }
|
我们可以把全树的居民分成两拨人:
1.原先在v的人,他们可以减少一步,故1 * (-z[v]) = -sz[v].
2.其他人,他们都需要多走一步,故1 * (total_weight - sz[v]) = total_weight - sz[v].
其时间复杂度为$O(n)$,可处理$N$更多的情况。