洛谷 P3354 [IOI2005]Riv 河流【树形dp】

博客围绕树节点选择问题展开。题目给出有n个节点的树,边有距离、点有权值,需选除根节点外k个节点求最小答案。分析时先尝试一种状态表示法,发现无法正确计算答案,后新增状态表示最近选择的点,更新状态并处理子树,最后合并状态。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


题目:

传送门


题意:

给出一棵有 n n n个节点的树,每条边有一个距离,每个点有一个权值
我们需要选出除根节点以外的 k k k个节点,每个点的答案为该点的点权向上直到第一个选出的节点,表示为 w ∗ d i s t w*dist wdist
求最小的答案


分析:

首先想一想我们需要记录哪些信息:现在在哪个点、这个点的选择如何、到目前为止一共选了几个点
于是有 f i , s , k f_{i,s,k} fi,s,k表示现在位于 i i i,选择状态为 s s s,在 i i i的子树中一共选择了 k k k个点
但在处理的过程中我发现,这样的状态表示方法看起来没啥问题,但好像答案无法正确的去计算
经过思考,我们发现某一部分的所有答案都会在一个固定的点,即连他们最近的选择的点,所以我们再新增一个状态,表示最近选择的点是哪个
更新状态 f i , f a , k & g i , f a , k f_{i,fa,k}\&g_{i,fa,k} fi,fa,k&gi,fa,k表示当前在 i i i,距离最近被选择的是 f a fa fa,以 i i i为根的子树内有 k k k个点被选择, f f f表示不选择 i i i g g g表示选择
在处理完一个点的整个子树后因为我们只需最优解,并不刻意 i i i的选择情况,所以将两个合并成一个就好咯


代码:

#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define LL long long
using namespace std;
inline LL read()
{
	LL s=0,f=1; char c=getchar();
	while(c<'0'||c>'9') {if(c=='-') f=-1;c=getchar();}
	while(c>='0'&&c<='9') {s=s*10+c-'0';c=getchar();}
	return s*f;
}
struct node{
	int to,next,w;
}e[205];
int ls[105],cnt=0;
void add(int x,int y,int w)
{
	e[cnt]=(node){y,ls[x],w};
	ls[x]=cnt++;
	return;
}
int f[105][105][55],g[105][105][55];
int s[105],size=0,dis[105],w[105];
int n=read(),k=read();
void dfs(int u,int fa)
{
	s[++size]=u;
	for(int i=ls[u];~i;i=e[i].next)
	{
		int v=e[i].to;
		if(fa==v) continue;
		dis[v]=dis[u]+e[i].w;
		dfs(v,u);
		for(int j=1;j<=size;j++)
		  for(int x=k;x>=0;x--)
		  {
		  	f[u][s[j]][x]+=f[v][s[j]][0];
		    g[u][s[j]][x]+=f[v][u][0];
		    for(int y=1;y<=x;y++)
		    {
		    	f[u][s[j]][x]=min(f[u][s[j]][x],f[u][s[j]][x-y]+f[v][s[j]][y]);
		    	g[u][s[j]][x]=min(g[u][s[j]][x],g[u][s[j]][x-y]+f[v][u][y]);
			}
		  }
	}
	for(int i=1;i<=size;i++)
	{
		f[u][s[i]][0]+=w[u]*(dis[u]-dis[s[i]]);
		for(int x=1;x<=k;x++)
		  f[u][s[i]][x]=min(f[u][s[i]][x]+w[u]*(dis[u]-dis[s[i]]),g[u][s[i]][x-1]);
	}
	size--;
	return;
}
int main()
{
	memset(ls,-1,sizeof(ls));
	for(int i=1;i<=n;i++)
	{
		int x=i+1;
		w[x]=read();
		int y=read()+1,W=read();
		add(x,y,W);add(y,x,W);
	}
	dfs(1,0);
	cout<<f[1][1][k];
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值