Luogu P3354 [IOI2005]Riv河流 题解———再加一维!再加一维!

Description

在这里插入图片描述
n ≤ 100 , k ≤ m i n ( n , 50 ) n≤100,k≤min(n,50) n100,kmin(n,50).

Solution

考虑树形 d p dp dp

状态设计为 d p i , j dp_{i,j} dpi,j表示在 i i i子树中建立了 j j j个伐木场的最少花费。

考虑状态转移。直接转移看起来十分困难,于是我们采用费用提前计算 d p dp dp技巧。即,对于每个节点都有一个贡献;具体的,第 i i i个节点的贡献为 w i × d i s ( i , p ) w_i×dis(i,p) wi×dis(i,p),这里 p p p表示离 i i i最近的伐木场

但是我们并不知道离 i i i最近的有伐木场的祖先节点在哪里……所以无法进行状态转移

根据上面的尝试,我们考虑再加一维。 d p i , j , k dp_{i,j,k} dpi,j,k表示在 i i i子树中建立了 k k k个伐木场,且离 i i i最近的有伐木场的祖先为 j j j时的最少花费

不难发现这是一个经典的背包模型。假设我们想要求出 d p n o w dp_{now} dpnow的所有状态。每次将 n o w now now已有的状态与孩子节点的状态进行合并,最后再对于每一个形如 d p n o w , p , k dp_{now,p,k} dpnow,p,k的状态加上一个提前计算的代价 ( p r e n o w − p r e p ) × a n o w (pre_{now}-pre_{p})×a_{now} (prenowprep)×anow即可。

树形背包是 O ( n 2 ) O(n^2) O(n2)的,外加了一个枚举离 i i i最近的有伐木场的祖先 p p p的代价,所以总时间复杂度为 O ( n 3 ) O(n^3) O(n3)

Code

#include <bits/stdc++.h>
using namespace std;
const int maxl=102,maxk=52;

int n,K,cnt=0,u,v,pos=0;
int head[maxl],a[maxl],s[maxl],f[maxl][maxl][maxk],g[maxl][maxl][maxk],pre[maxl];

struct edge{int nxt,to,dis;}e[2*maxl];
void add_edge(int u,int v,int w){
	cnt++;
	e[cnt].to=v,e[cnt].nxt=head[u],e[cnt].dis=w;head[u]=cnt;
}

void dfs(int now,int fath,int up){
	pre[now]=pre[fath]+up;
	s[++pos]=now;
	for (int i=head[now];i;i=e[i].nxt){
		int y=e[i].to;
		if (y==fath)  continue;
		dfs(y,now,e[i].dis);
		
		for (int j=1;j<=pos;j++){
			int tmp=s[j];
			for (int k=K;k>=0;k--){
				f[now][tmp][k]+=f[y][tmp][0];
				g[now][tmp][k]+=f[y][now][0];
				for (int x=0;x<=k;x++){
					f[now][tmp][k]=min(f[now][tmp][k],f[now][tmp][x]+f[y][tmp][k-x]);
					g[now][tmp][k]=min(g[now][tmp][k],g[now][tmp][x]+f[y][now][k-x]);
				}
			}
		}
	}
	for (int i=1;i<=pos;i++){
		int tmp=s[i];
		for (int j=0;j<=K;j++){
			f[now][tmp][j]=f[now][tmp][j]+a[now]*(pre[now]-pre[tmp]);
			if (j>=1)  f[now][tmp][j]=min(f[now][tmp][j],g[now][tmp][j-1]);
		}
	}
	pos--;
}

signed main(){
	cin>>n>>K;
	for (int i=1;i<=n;i++){
		cin>>a[i]>>u>>v;
		add_edge(i,u,v),add_edge(u,i,v);
	}
	dfs(0,-1,0);
	cout<<f[0][0][K]<<endl;
	
	return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值