树形 dp 专题
小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;
}
没有上司的舞会
最大独立集,一条边的两个端点只能选一个,问最多选多少个点。
#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;
}
Strategic 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;
}
Cell 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;
}
二叉苹果树
#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;
}
树上子链
两边 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;
}
Rinne Loves Edges
\(f[x]\)表示\(x\)的子树上所以叶子与根断开的最小代价。
\[f[x]=\sum \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;
}
吉吉王国
与上一题基本相同,在加一个二分答案,删边的过程中只删长度小于\(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;
}