P3354 [IOI2005] Riv 河流

Fire_Raku發表於2024-04-20

P3354 [IOI2005] Riv 河流

樹形 dp

我們很容易套路地用 \(f_{u,i}\) 表示在 \(u\) 子樹中,\(u\) 節點放了 \(i\) 個伐木場的最小花費。但是這樣無法轉移,原因是無法表示路徑長度,也無法知道運送數量。

所以我們現在考慮增加狀態,能夠表示出距離。只考慮 \(u\) 節點的花費,其木頭要運送上去,一定會走一段 \(u\) 到某個祖先的一段距離,如果這個祖先有伐木場,那麼距離就是這段長度。於是設 \(f_{u,i,j}\) 表示 \(u\) 子樹中放了 \(j\) 個伐木場,在祖先 \(i\) 上有一個伐木場的最小花費。此時因為轉移時 \(u\) 子樹內的節點已經計算過了,轉移完只需要計算 \(u\) 節點的運送花費。

但是仔細一想還是不夠,如果 \(u\) 節點有伐木場,那麼在轉移時 \(v\) 節點的距離就是 \(u\)\(v\)。所以我們還需要加一維表示 \(u\) 建不建伐木場(或者開一個新陣列)。

轉移時細節的地方:我們在轉移時可以先欽定 \(f\)\(u\) 節點不建伐木場,\(g\) 為建伐木場,最後把 \(g\) 合併到 \(f\) 上即可。

複雜度 \(O(n^2k^2)\)

#include <bits/stdc++.h>
#define pii std::pair<int, int>
#define fi first
#define se second
#define pb push_back

typedef long long i64;
const i64 iinf = 0x3f3f3f3f, linf = 0x3f3f3f3f3f3f3f3f;
const int N = 110;
int n, k, cnt;
struct node {
	int to, nxt, w;
} e[N];
int h[N], w[N];
void add(int u, int v, int w) {
	e[++cnt].to = v;
	e[cnt].nxt = h[u];
	e[cnt].w = w;
	h[u] = cnt;
}
int st[N], top;
int f[N][N][N], g[N][N][N], dis[N];
void dfs(int u) {
	st[++top] = u;
	for(int _ = h[u]; _; _ = e[_].nxt) {
		int v = e[_].to;
		dis[v] = dis[u] + e[_].w;
		dfs(v);
		for(int i = 1; i <= top; i++) {
			for(int j = k; j >= 0; j--) {
				f[u][st[i]][j] += f[v][st[i]][0];
				g[u][st[i]][j] += f[v][u][0]; //這裡要先轉移一次,否則無法正確轉移
				for(int x = 1; x <= j; x++) {
					f[u][st[i]][j] = std::min(f[u][st[i]][j], f[u][st[i]][j - x] + f[v][st[i]][x]);
					g[u][st[i]][j] = std::min(g[u][st[i]][j], g[u][st[i]][j - x] + f[v][u][x]);
				}
			}	
		}
	}
	for(int i = 1; i <= top; i++) {
		for(int j = k; j >= 1; j--) {
			f[u][st[i]][j] = std::min(f[u][st[i]][j] + w[u] * (dis[u] - dis[st[i]]), g[u][st[i]][j - 1]);
		}
		f[u][st[i]][0] += w[u] * (dis[u] - dis[st[i]]);
	}
	top--;
}
void Solve() {
	std::cin >> n >> k;
	for(int i = 1; i <= n; i++) {
		int v, d;
		std::cin >> w[i] >> v >> d;
		add(v, i, d);
	}
	dfs(0);
	std::cout << f[0][0][k] << "\n";
}
int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    
	Solve();

	return 0;
}