1. 程式人生 > 其它 >【賽前複習】樹形DP

【賽前複習】樹形DP

媽媽再也不用擔心我的樹形DP了!!1((

前言:

沒有,下一個

學習部落格:

簡單介紹:

題目

Fire

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

Rebuilding Roads
or
Luogu重建道路

給定一棵 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}\)

中被當成刪邊, 所以要 -1

初始值的設定真的好妙

最後答案統計也要注意,因為除樹根之外的點在 \(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

月亮墜入不見底的河 星星垂眸驚動來舸