树形 dp 专题

题单

1|0小G有一个大树


#include <bits/stdc++.h> using namespace std; #define int long long int32_t main() { ios::sync_with_stdio(0), cin.tie(0); int n; while (cin >> n) { vector<vector<int>> e(n + 1); for (int i = 1, x, y; i < n; i++) cin >> x >> y, e[x].push_back(y), e[y].push_back(x); vector<int> tot(n + 1), fa(n + 1); fa[1] = -1; auto dfs = [&](auto &&self, int x) -> void { tot[x] = 1; for (auto y: e[x]) { if (y == fa[x]) continue; fa[y] = x; self(self, y); tot[x] += tot[y]; } }; dfs(dfs, 1); int res = LLONG_MAX , id = -1 ; for( int x = 1 , ans ; x <= n ; x ++ ){ ans = n - tot[x]; for( auto y : e[x] ){ if( y == fa[x] ) continue; ans = max( ans , tot[y] ); } if( ans < res ) res = ans , id = x; } cout << id << " " << res << "\n"; } return 0; }

2|0没有上司的舞会


最大独立集,一条边的两个端点只能选一个,问最多选多少个点。

#include <bits/stdc++.h> using namespace std; #define int long long int32_t main() { ios::sync_with_stdio(0), cin.tie(0); int n; cin >> n; vector<int> h(n + 1); for (int i = 1; i <= n; i++) cin >> h[i]; vector<vector<int>> e(n + 1); int root = n * (n + 1) / 2; for (int i = 1, x, y; i < n; i++) cin >> y >> x, e[x].push_back(y), root -= y; vector<array<int, 2>> f(n+1); auto dfs = [e, h, &f](auto &&self, int x) -> void { f[x][1] = h[x]; for (auto y: e[x]) { self(self, y); f[x][0] += max(f[y][0], f[y][1]); f[x][1] += f[y][0]; } return; }; dfs(dfs, root); cout << max(f[root][1], f[root][0]); return 0; }

3|0Strategic game


最小点覆盖,选一个点可以把相邻的边覆盖,问最少选多少个点可以把所有的边覆盖。

#include <bits/stdc++.h> using namespace std; #define int long long int32_t main() { int n; while (cin >> n) { vector<vector<int>> e(n); vector<array<int, 2>> f(n); int root = n * (n - 1) / 2; for (int x, y, t, i = 1; i <= n; i++) for( x = read() , t = read() ; t ; t -- ) y = read(), e[x].push_back(y), root -= y; auto dfs = [e, &f](auto &&self, int x) -> void { f[x][1] = 1; for (auto y: e[x]) { self(self, y); f[x][1] += min(f[y][0], f[y][1]); f[x][0] += f[y][1]; } }; dfs(dfs, root); cout << min(f[root][1], f[root][0]) << "\n"; } return 0; }

4|0Cell Phone Network


最小支配集,选一个点可以把相邻的点覆盖,问最少选多少个点可以把所有的点覆盖。

#include <bits/stdc++.h> using namespace std; constexpr int inf = 1e9; int32_t main() { ios::sync_with_stdio(0), cin.tie(0); int n; cin >> n; vector<vector<int>> e(n + 1); for (int i = 1, x, y; i < n; i++) cin >> x >> y, e[x].push_back(y), e[y].push_back(x); vector<array<int, 3>> f(n + 1); // f[x][0] x 被自己覆盖 // f[x][1] x 被儿子覆盖 // f[x][2] x 被父亲覆盖 auto dfs = [&f, e](auto &&self, int x, int fa) -> void { f[x][0] = 1; f[x][1] = inf; f[x][2] = 0; int inc = inf; for (auto y: e[x]) { if (fa == y) continue; if (f[x][1] == inf) f[x][1] = 0; self(self, y, x); f[x][0] += min({f[y][0], f[y][1], f[y][2]}); f[x][2] += min(f[y][0], f[y][1]); f[x][1] += min(f[y][0], f[y][1]), inc = min(inc, f[y][0] - f[y][1]); } f[x][1] += max(0, inc); return; }; dfs(dfs, 1, -1); cout << min(f[1][0], f[1][1]); return 0; }

5|0二叉苹果树


#include <bits/stdc++.h> using namespace std; constexpr int inf = 1e9; typedef pair<int, int> pii; int32_t main() { ios::sync_with_stdio(0), cin.tie(0); int n, m; cin >> n >> m; vector<vector<pii>> e(n + 1); for (int i = 1, x, y, z; i < n; i++) cin >> x >> y >> z , e[x].emplace_back(y, z), e[y].emplace_back(x, z); vector<vector<int>> g(n + 1); vector<int> val(n + 1); auto dfs = [e, &g, &val](auto &&self, int x, int fa) -> void { for (auto [y, w]: e[x]) { if (y == fa) continue; g[x].push_back(y), val[y] = w; self(self, y, x); } }; dfs(dfs, 1, -1); vector<vector<int>> f(n + 1, vector<int>(m + 1, -inf)); auto dp = [g, val, &f](auto &&self, int x, int t) -> int { if (f[x][t] != -inf) return f[x][t]; if (t == 0) return f[x][t] = 0; if (g[x].empty()) return f[x][t] = 1 - inf; int l = g[x][0], r = g[x][1]; f[x][t] = max(self(self, l, t - 1) + val[l], self(self, r, t - 1) + val[r]); for (int j = 0; j <= t - 2; j++) f[x][t] = max(f[x][t], self(self, l, j) + self(self, r, t - 2 - j) + val[l] + val[r]); return f[x][t]; }; cout << dp(dp, 1, m); return 0; }

6|0树上子链


两边 dfs 求直径

#include<bits/stdc++.h> using namespace std; #define mp make_pair #define int long long int res = LLONG_MIN; vector<int> v, f; vector<vector<int>> e; void dfs(int x, int fa) { f[x] = v[x]; for (auto y: e[x]) { if (y == fa) continue; dfs(y, x); res = max(res, f[x] + f[y]); f[x] = max(f[x], f[y] + v[x]); } res = max(res, f[x]); } int32_t main() { ios::sync_with_stdio(false), cin.tie(nullptr); int n; cin >> n; v.resize(n + 1); for (int i = 1; i <= n; i++) cin >> v[i]; e.resize(n + 1); for (int i = 1, x, y; i < n; i++) cin >> x >> y, e[x].push_back(y), e[y].push_back(x); f.resize(n + 1); dfs(1, -1); cout << res << "\n"; return 0; }

树形 DP

#include <bits/stdc++.h> using namespace std; #define int long long constexpr int inf = 1e18; typedef pair<int, int> pii; int32_t main() { ios::sync_with_stdio(0), cin.tie(0); int n; cin >> n; vector<int> val(n + 1); vector<vector<int>> e(n + 1); for (int i = 1; i <= n; i++) cin >> val[i]; for (int i = 1, x, y; i < n; i++) cin >> x >> y, e[x].push_back(y), e[y].push_back(x); vector<int> f(n + 1 , - inf ), dis(n + 1); auto dfs = [e, val, &f, &dis](auto &&self, int x, int fa) -> void { multiset<int> t; for (auto y: e[x]) { if (y == fa) continue; self(self, y, x); f[x] = max(f[x], f[y]); dis[x] = max(dis[x], dis[y] + val[y]); t.insert(dis[y] + val[y]); if (t.size() == 3) t.erase(t.begin()); } int w = val[x]; for (auto i: t) w += i; f[x] = max(f[x], w); return; }; dfs(dfs, 1, -1); cout << f[1]; return 0; }

7|0Rinne Loves Edges


f[x]表示x的子树上所以叶子与根断开的最小代价。

f[x]=min(f[y],w)

其中y表示x的儿子,w表示x,y之间边的边权。

#include <bits/stdc++.h> using namespace std; #define int long long using pii = pair<int, int>; int32_t main() { ios::sync_with_stdio(false), cin.tie(nullptr); int n, m, s; cin >> n >> m >> s; vector<vector<pii>> e(n + 1); for (int i = 1, x, y, z; i <= m; i++) cin >> x >> y >> z, e[x].emplace_back(y, z), e[y].emplace_back(x, z); vector<int> f(n + 1, LLONG_MAX); auto dp = [&f, e](auto &&self, int u, int fa) -> int { if (f[u] != LLONG_MAX) return f[u]; if (e[u].size() == 1 and e[u].front().first == fa) return --f[u]; f[u] = 0; for (auto [v, w]: e[u]) if( v != fa ) f[u] += min(self(self, v, u), w); return f[u]; }; cout << dp(dp, s, -1); return 0; }

8|0吉吉王国


与上一题基本相同,在加一个二分答案,删边的过程中只删长度小于mid的即可。

#include <bits/stdc++.h> using namespace std; #define int long long using pii = pair<int, int>; constexpr int inf = 1e9; int32_t main() { ios::sync_with_stdio(false), cin.tie(nullptr); int n, m; cin >> n >> m; vector<vector<pii>> e(n + 1), g(n + 1); for (int i = 1, u, v, w; i < n; i++) cin >> u >> v >> w, e[u].emplace_back(v, w), e[v].emplace_back(u, w); auto dfs = [e, &g](auto self, int u, int fa) -> void { for (const auto &[v, w]: e[u]) if (v != fa) g[u].emplace_back(v, w), self(self, v, u); }; dfs(dfs, 1, -1); int l = 0, r = 1e3, res = -1; for (int mid; l <= r;) { mid = (l + r) >> 1; vector<int> f(n + 1, inf); auto dp = [g, &f, mid](auto self, int u) { if (f[u] != inf) return f[u]; if (g[u].empty()) return --f[u]; f[u] = 0; for (const auto [v, w]: g[u]) { if (w <= mid) f[u] += min(self(self, v), w); else f[u] += self(self, v); } return f[u]; }; if( dp(dp,1) <= m ) res = mid , r = mid - 1; else l = mid + 1; } cout << res << "\n"; return 0; }

__EOF__

本文作者PHarr
本文链接https://www.cnblogs.com/PHarr/p/17697883.html
关于博主:前OIer,SMUer
版权声明CC BY-NC 4.0
声援博主:如果这篇文章对您有帮助,不妨给我点个赞
posted @   PHarr  阅读(9)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
历史上的今天:
2022-09-12 2020年湖南省大学生计算机程序设计竞赛
点击右上角即可分享
微信分享提示