P3354 Riv河流

题目简述

一个颗以 0 号节点为根的树,每个点产一些木头,0 号节点有一个伐木场,还需要在 $k$ 个结点修建伐木场。对于每一个点,它的花费是它木头产量乘它到 0 号节点的路径中离它最近的伐木场的距离。最小化总花费。

思路

首先考虑设 $f_{i,j}$ 表示以 $i$ 为根的子树中建了 $j$ 个伐木场,子树中的最小花费。但是有一个明显的问题:如果 $i$ 没有建伐木场,对于子树中的一些节点,我们就不知道它们的木头运到了哪里。所以不这道题中不仅要考虑子树的范围,还需要用离根节点最近的伐木场距离来限制状态。每个节点的木头只能向跟的方向运,所以即使离根节点最近的伐木场不同,对这颗子树的花费产生贡献的点是相同的。

设 $f_{x,i,j}$ 表示以 $x$ 为根的子树中建了 $j$ 个伐木场,$x$ 没有建伐木场,距离 $x$ 最近的伐木场在 $i$; $g_{x,i,j}$ 表示以 $x$ 为根的子树中建了 $j$ 个伐木场,$x$ 建了伐木场,距离 $x$ 最近的伐木场在 $i$ —— 当然也可以用 $f_{x,i,j,\ 0/1}$ 表示这两种状态。下面用 0 表示没有建,1 表示建了。因为 DFS 回溯时更新完的状态不会再被改变,只会被使用,于是我们可以将 0 和 1 记录的答案汇总到一起(下面的部分都是汇总到 0),于是不难得到转移方程。这个点的所有更新结束后再汇总信息。

f[x][father][subk][0] = min(f[x][father][subk][0], f[thisSon][father][l][0] + f[x][father][subk-l][0]);
					f[x][father][subk][1] = min(f[x][father][subk][1], f[thisSon][x][l][0] + f[x][father][subk-l][1]);

更新时涉及到枚举根节点祖先的问题,我们可以建一个栈,在第一次 DFS 到某个点时将其入栈,在这个点的更新和信息合并结束后再将其出栈,枚举一个点的祖先是只需要遍历一遍栈就可以了。

Code

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<climits>
#define int long long
using namespace std;
const int MaxN = 102;
int n, k, w[MaxN], d[MaxN], head[MaxN], cntEdge, f[MaxN][MaxN][MaxN][2], stack[MaxN], top, dis[MaxN];
bool visited[MaxN];
struct Edge{
	int destination, value, nextEdge;
}edge[MaxN*2];
#define thisSon edge[i].destination

inline int Read(){
	int num = 0, op = 1;
	char ch = getchar();
	while(!isdigit(ch)){
		if(ch == '-') op = -1;
		ch = getchar();
	}
	while(isdigit(ch)){
		num = num * 10 + ch - '0';
		ch = getchar();
	}
	return num * op;
}

inline void AddEdge(int u, int v, int w){
	cntEdge++;
	edge[cntEdge].destination = v;
	edge[cntEdge].value = w;
	edge[cntEdge].nextEdge = head[u];
	head[u] = cntEdge;
	return;
}

inline void DFS(int x){
	stack[++top] = x;
	for(int i = head[x]; i; i = edge[i].nextEdge){
		if(visited[thisSon]) continue;
		visited[thisSon] = true;
		dis[thisSon] = dis[x] + edge[i].value;
		DFS(thisSon);
		for(int j=top; j>=1; j--){
			int father = stack[j];
			for(int subk = k; subk>=0; subk--){
				f[x][father][subk][1] += f[thisSon][x][0][0];
				f[x][father][subk][0] += f[thisSon][father][0][0];
				for(int l=subk; l>=0; l--){
					f[x][father][subk][0] = min(f[x][father][subk][0], f[thisSon][father][l][0] + f[x][father][subk-l][0]);
					f[x][father][subk][1] = min(f[x][father][subk][1], f[thisSon][x][l][0] + f[x][father][subk-l][1]);
				}
			}
		}
	}
	for(int i=1; i<=top; i++){
		int father = stack[i];
		for(int j=k; j>=0; j--){
			if(j >= 1) f[x][father][j][0] = min(f[x][father][j][0] + w[x] * (dis[x]-dis[father]), f[x][father][j-1][1]);
			else f[x][father][j][0] += w[x] * (dis[x]-dis[father]);
		}
	}
	top--;
	return;
}

signed main(){
	n = Read(), k = Read();
	for(int i=1; i<=n; i++){
		w[i] = Read();
		int father = Read();
		d[i] = Read();
		AddEdge(father, i, d[i]);
	}
	visited[0] = 1;
	DFS(0);
	printf("%lld", f[0][0][k][0]);
	return 0;
}
Sorry potatoler, but I cannot recognize you.
Built with Hugo
Theme Stack designed by Jimmy