【賽前複習】樹形DP
前言:
沒有,下一個
學習部落格:
簡單介紹:
題目
Fire
給定一棵樹,對於任意節點 \(i\) 需要滿足至少一個條件。
1.可以花費一定代價 \(w_i\) 建消防站;
2.離它最近的消防站的之間的距離不超過 \(D_i\),
求滿足條件的最小花費。
\(dp_{i, j}\): 表示城市 \(i\) 的負責點是 \(j\) 的情況。
再來一個陣列 \(g_i\) 表示 \(i\) 的子樹內負責點不定的最小代價
\(dp_{i, j}\) 向上傳遞,由兒子傳遞到父親。
對於一棵樹來說,父親為 \(x\), 兒子為 \(y\)。
- 如果 \(x\), \(y\) 由同一個節點 \(a\)
\(dp_{x, a} += dp_{y,a} - w_a\);
直接減去重複計算的 \(w_a\) 即可
- 如果x, y 不由同一個節點負責,那就不用管 y 具體由哪個節點負責,直接用 \(g_y\) 轉移
\(dp_{x,a} += g_y\)
關於 \(g_x\) 的轉移就是,每次列舉 負責 \(x\) 的節點 \(i\) , 處理完後,取最小值
\(g_x = \min\) { \(dp_{x,i}\) } \((dis_{x, i} <= D_x)\)
#include<cstdio> #include<algorithm> #include<cstring> #include<queue> using namespace std; const int N = 1005; int t, n, D[N], W[N], tot, head[N], nex[N << 1], to[N << 1], w[N << 1], dis[N][N]; void add(int x, int y, int z) { to[++tot] = y, nex[tot] = head[x], head[x] = tot, w[tot] = z; } void dfs(int st, int x, int f) { int ver; for(int i = head[x]; i; i = nex[i]) { ver = to[i]; if(ver == f) continue; dis[st][ver] = dis[st][x] + w[i]; dfs(st, ver, x); } } int dp[N][N], g[N]; void solve(int x, int f) { int ver; for(int i = head[x]; i; i = nex[i]) { ver = to[i]; if(ver == f) continue; solve(ver, x); } for(int i = 1; i <= n; i ++) { if(dis[i][x] > D[x]) continue; dp[x][i] = W[i]; for(int j = head[x]; j; j = nex[j]) { ver = to[j]; if(ver == f) continue; dp[x][i] += min(dp[ver][i] - W[i], g[ver]); } g[x] = min(g[x], dp[x][i]); } } int main() { int u, v, ww; scanf("%d", &t); while(t --) { scanf("%d", &n); tot = 0; memset(head, 0, sizeof(head)); memset(dp, 0x3f, sizeof(dp)); memset(g, 0x3f, sizeof(g)); for(int i = 1; i <= n; i ++) scanf("%d", &W[i]); for(int i = 1; i <= n; i ++) scanf("%d", &D[i]); for(int i = 1; i < n; i ++) { scanf("%d %d %d", &u, &v, &ww); add(u, v, ww), add(v, u, ww); } for(int i = 1; i <= n; i ++) { dis[i][i] = 0; dfs(i, i, 0); } solve(1, 0); printf("%d\n", g[1]); } return 0; }
Rebuilding Roads
給定一棵 n 個節點的樹,問最少刪去多少條邊,能夠得到一個節點個數為 p 的連通塊。
\(dp_{i,j}\) 表示 以 \(i\) 為根的子樹 保留 \(j\) 個點聯通的最小代價
重點在 \(dp\) 的初始條件的設定。
以及式子的小細節處理
$ dp_{i, j} = min(dp_{i, j}, dp_{i, j - k} + dp_{ver, k} - 1) $
因為 \(x\) 和 \(ver\) 之間相連是存在一條邊的, 而那條邊沒有算進 \(dp_{ver, k}\) 中, 可能會在 \(dp_{x, j - k}\)
初始值的設定真的好妙
最後答案統計也要注意,因為除樹根之外的點在 \(DP\) 時都沒有考慮 父親, 所以最後還要額外刪去 和父親相連的那條邊
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 155;
int n, p, rt, ans = 1e9, fa[N], tot, head[N], nex[N], to[N], dp[N][N], in[N], out[N];
void add(int x, int y) {
to[++tot] = y, nex[tot] = head[x], head[x] = tot;
}
int siz[N];
void dfs(int x) {
int ver;
siz[x] = 1;
for(int i = head[x]; i; i = nex[i]) {
ver = to[i];
dfs(ver);
siz[x] += siz[ver];
}
for(int i = head[x]; i; i = nex[i]) {
ver = to[i];
for(int j = siz[x]; j; j --) {
for(int k = 1; k < j; k ++) {
dp[x][j] = min(dp[x][j], dp[x][j - k] + dp[ver][k] - 1);
}
}
}
}
int main() {
int u, v;
memset(dp, 0x3f, sizeof(dp));
scanf("%d %d", &n, &p);
for(int i = 1; i < n; i ++) {
scanf("%d %d", &u, &v);
out[u] ++, in[v] ++;
add(u, v);
}
for(int i = 1; i <= n; i ++) {
if(!in[i]) rt = i;
dp[i][1] = out[i];
}
dfs(rt);
ans = dp[rt][p];
for(int i = 1; i <= n; i ++) ans = min(ans, dp[i][p] + 1);
printf("%d", ans);
return 0;
}
Road Improvement
轉載請註明原文連結:https://www.cnblogs.com/Spring-Araki/p/15419596.html
月亮墜入不見底的河 星星垂眸驚動來舸