P3354 [IOI2005]Riv 河流

大致题意

给一颗\(n\)个节点的树,根节点是一个伐木场,每个节点都有一个村庄,每个村庄的每根木头运送到其父亲的代价是\(d_i\),现在要建立\(k\)个伐木场,每个村庄有\(w_i\)根木头要沿树边运到最近的伐木场,求最小代价

分析

树形\(dp\)

一开始比较容易想到的是设\(f(i,j)\)表示在\(i\)的子树中建立\(j\)个伐木场所花费的最小代价

发现无法得知一个节点的上一个伐木场在哪,即无法计算代价,考虑在状态中记录上一个伐木场的位置

\(f(i,j,k)\)表示上一个伐木场的位置是\(j\),\(i\)的子树中建立\(j\)个伐木场所花费的代价

有显然的转移:

$f(u,j,k) = min\{f(u,j,k-l)+min(f(v,v,l),f(v,j,l))\}(1≤l≤k)$

亿点细节:

  • 记得删除不合法的状态,详细见代码

  • 枚举\(j\)时要先判断\(j\)是否在\(u\)的祖先中

\(code\)

#include<bits/stdc++.h>
using namespace std;
#define inf (1<<30)
const int MAXN = 110;
int head[MAXN<<1];
struct e{
	int v,w,next;
}edge[MAXN<<1];
int cnt = 0;
int n,K;
int c[MAXN],f[MAXN][MAXN][MAXN];
int is_fa[MAXN];
int dis[MAXN];
void add(int u,int v,int w){
	edge[++cnt].v = v;
	edge[cnt].next = head[u];
	edge[cnt].w = w;
	head[u] = cnt;
}
void dfs(int u){
	is_fa[u] = true;
	for(int i=0;i<=n;i++) for(int j=0;j<=K;j++){
		if(!is_fa[i]) f[u][i][j] = inf;//除去不合法状态
		else if(i==0||u!=i) f[u][i][j] = (dis[u] - dis[i])*c[u];
		else if(j!=0) f[u][i][j] = 0;
		else f[u][i][j] = inf;//显然在u=i且j=0时状态不合法
	}
	for(int i=head[u];i;i=edge[i].next){
		int v = edge[i].v;
		dis[v] = edge[i].w+dis[u];
		dfs(v);
		for(int j=0;j<=n;j++){
			if(!is_fa[j]) continue;//判断是否为祖先
			for(int k=K;k>=0;k--){
				f[u][j][k]+=f[v][j][0];
				for(int s=1;s<=k;s++){
					f[u][j][k] = min(f[u][j][k],f[u][j][k-s]+min(f[v][v][s],f[v][j][s]));
				}
			}
		}
	}
	is_fa[u] = false;
}
int main(){
	cin>>n>>K;
	for(int i=1;i<=n;i++){
		int u,w;
		cin>>c[i]>>u>>w;
		add(u,i,w);
		dis[i] = w;
	}
	dfs(0);
	cout<<f[0][0][K];
}


posted @ 2020-11-14 17:39  xcxc82  阅读(92)  评论(0编辑  收藏  举报