bzoj 1812 [IOI2005] riv (树形dp,树上背包)

版权声明:版权声明:本文为博主原创文章,未经博主允许不得转载,欢迎添加友链。 https://blog.csdn.net/zzk_233/article/details/82937521

这道题比较有难度。。。首先可以想到f[i][k]表示i点的子树中用了k个伐木场。但是这显然没法转移。那么我们加上一维,

f[i][j][k]表示i点的子树内用了k个伐木场并且上一个用的位置为j,而当前点可选可不选(看第二维的状态),之后用子树更新即可

合并的时候和更新的时候要特殊考虑i==j的情况。

枚举这个点其他子树内用了w个,这个子树为k-w

当不选择这个点时f[i][j][k]=min(f[to][j][k-w]+f[i][j][w])

选择这个点时为dp[i][i][k]=min(dp[to][i][k-w]+dp[i][i]w])

最后合并

f[i][j][k]+=val[i]*dis[i][j]

f[i][j][k]=min(f[i][j][k],f[i][i][k])

#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
using namespace std;
int n,m,tot=0x3f3f3f3f,fa[205],dis[205][205],f[205][205][55],val[205];
struct node
{
	int to;
	int nxt;
}edge[40005];
int head[205];
int cnt=1;
void init()
{
	memset(head,-1,sizeof(head));
	memset(fa,-1,sizeof(fa));
}
void add(int from,int to)
{
	edge[cnt].to=to;
	edge[cnt].nxt=head[from];
	head[from]=cnt++;
}
void dfs(int u)
{
	int la=u;
	for(int i=fa[u];i!=-1;i=fa[i])
	{
		dis[u][i]=dis[u][la]+dis[la][i];
		la=i;
	} 
	if(head[u]==-1)
	{
		for(int i=fa[u];i!=-1;i=fa[i])
		{
			f[u][i][0]=dis[u][i]*val[u];
		}
		return;
	}
	if(u)f[u][u][0]=0x3f3f3f3f;
	for(int i=head[u];i!=-1;i=edge[i].nxt)
	{
		int to=edge[i].to;
		dfs(to);
		for(int j=fa[u];j!=-1;j=fa[j])
		{
			for(int k=m;k>=0;k--)
			{
				int tmp=0x3f3f3f3f;
				for(int w=0;w<=k;w++)
				{
					tmp=min(tmp,f[u][j][w]+f[to][j][k-w]);
				}
				f[u][j][k]=tmp;
			}
		}
		for(int k=m;k>=0;k--)
		{
			int tmp=0x3f3f3f3f;
			for(int w=(u!=0);w<=k;w++)
			{
				tmp=min(tmp,f[u][u][w]+f[to][u][k-w]);
			}
			f[u][u][k]=tmp;
		}
	}
	for(int i=fa[u];i!=-1;i=fa[i])
	{
		for(int j=0;j<=m;j++)
		{
			f[u][i][j]+=val[u]*dis[u][i];
			f[u][i][j]=min(f[u][i][j],f[u][u][j]);
		}
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	init();
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&val[i]);
		int a;
		scanf("%d%d",&fa[i],&a);
		add(fa[i],i);
		dis[i][fa[i]]=a;
	}
	dfs(0);
	printf("%d",f[0][0][m]);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/zzk_233/article/details/82937521