「解题报告」P3354 [IOI2005]Riv 河流

「解题报告」P3354 [IOI2005]Riv 河流

题目传送门

一道很恶心的树形dp 但是我喜欢

题目大意:

一片海旁边有一条树状的河,入海口有一个大伐木场,每条河的分叉处都有村庄。建了伐木场的村庄可以直接处理木料,否则要往下游的伐木场运,运费为每吨每千米 \(1\) 分钱。现在要在一些村庄建 \(k\) 个伐木场,求建完之后最小的运输费用。

设计状态:

\[f(u,z,k,l): \begin{cases} u:以第i个点为根的子树 \\ z:离点i最近的伐木场的位置(祖先) \\ k:该子树被分配了k个伐木场 \\ l:0或1,表示是否在此节点建伐木场 \end{cases} \]

转移

\[f_{u,z,k}= \begin{cases} f_{u,z,k,0}=\min\limits_{v\in son}f_{u,z,k-l,0}+f_{v,z,l,0}\\ f_{u,z,k,1}=\min\limits_{v\in son}f_{u,z,k-l,1}+f_{v,u,l,0}\ \ (此时u有伐木场,它的儿子们可以直接运往它) \end{cases} \]

\(l\) 为给一个儿子分配的 \(k\) 个伐木场

然后对是否建伐木场进行合并,即:

\[f_{u,z,k}=\min(f_{u,z,k,0}+w_u*(d_u-d_z)\ ,\ f_{u,z,k-1,1}) \]

初始状态

\[f_{u,z,j,0}=f_{u,z,j,0}+f_{v,z,0,0}\\ f_{u,z,j,1}=f_{u,z,j,1}+f_{v,u,0,0} \]

代码:

#include <bits/stdc++.h>
#define _for(i,a,b) for(int i=a;i<=b;++i)
#define for_(i,a,b) for(int i=a;i>=b;--i)
#define ll long long
using namespace std;
const int N=110,inf=0x3f3f3f3f;
ll n,k,a,fm[N],c,w[N],d[N],v;
ll f[N][N][N][2];
vector<ll>s[N];
//fm:祖先,d:深度,s:儿子们
void dp(ll u){
	fm[++c]=u;//把该点压入祖先们
	int sz=s[u].size();
	_for(i,0,sz-1){
		int v=s[u][i];
		d[v]+=d[u];
		dp(v);
		_for(i,1,c){//重点部分!!
			for_(j,k,0){
				//初始化
				f[u][fm[i]][j][0]+=f[v][fm[i]][0][0];
				f[u][fm[i]][j][1]+=f[v][u][0][0];
				_for(l,0,j){
					//转移
					f[u][fm[i]][j][0]=min(f[u][fm[i]][j][0],f[u][fm[i]][j-l][0]+f[v][fm[i]][l][0]);
					f[u][fm[i]][j][1]=min(f[u][fm[i]][j][1],f[u][fm[i]][j-l][1]+f[v][u][l][0]);
				}
			}
		}
	}_for(i,1,c){//合并
		f[u][fm[i]][0][0]+=w[u]*(d[u]-d[fm[i]]);
		_for(j,1,k)f[u][fm[i]][j][0]=min(f[u][fm[i]][j][0]+w[u]*(d[u]-d[fm[i]]),f[u][fm[i]][j-1][1]);
	}
	--c;//把该点弹出去
}
int main(){
	scanf("%lld%lld",&n,&k);
	_for(i,1,n){
		scanf("%lld%lld%lld",&w[i],&a,&d[i]);
		s[a].push_back(i);
	}
	dp(0);
	printf("%lld\n",f[0][0][k][0]);
	return 0;
}
posted @ 2022-01-15 15:15  K8He  阅读(72)  评论(0)    收藏  举报