1. 程式人生 > >bzoj 1812 [IOI2005] riv (樹形dp,樹上揹包)

bzoj 1812 [IOI2005] riv (樹形dp,樹上揹包)

這道題比較有難度。。。首先可以想到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;
}