Riv——树形DP

题目

题目链接

思路

整道题看起来有很多要点要考虑

最先想出的DP方程

dp[i][j]表示第i个结点所在子树建j个伐木场的最小花费
dp[i][j] = min(dp[v][k] + cost)

看起来挺好的,时间复杂度也不高,但问题来了

cost怎么算啊

不知道剩余木材 不知道走的距离

于是老师说 \(\color{pink}{要三维}\)

定义dp[i][j][k]表示第i个结点所在子树建j个伐木场且最近的一个伐木场在k

然后我 \(\color{pink}{没有想出来}\)

于是去看了洛谷题解,但觉得讲的不是很清楚,特于此再写一篇

定义四维的

dp[i][j][k]表示第i个结点最近且建了伐木场的为j 共建k个的最小花费 1 表示在i点建了

注意 上面的最近伐木场指的是祖先结点上的

记录祖先 考虑用

ancestor[++tot] = x;放在dfs里的首行
tot--;放在末行

非常好理解

记录距离和木材数

只要知道应前往之地和现在所在之地 就可以用这种方式表示

u:现在所在之地
v:应前往之地
dep[i]记录i到根1的距离
即dis(u,v) = |dep[v] - dep[u]|

木材数则从这里一次性运到伐木场,所以不记录中间状态

wood[i]表示i点的木材

然后就是最重要的状态转移方程了

dp[x][fa][k][0] = Min(dp[x][fa][k][0],dp[v][fa][l][0] + dp[x][fa][k - l][0]);
dp[x][fa][k][1] = Min(dp[x][fa][k][1],dp[v][x][l][0] + dp[x][fa][k - l][1]);	

然后就有非常讲究的事儿了

for(j = 1;j <= tot;j++)
{
//j的顺序无所谓
	int fa = ancestor[j];
	for(k = maxk;k >= 0;k--)
	{
	//k的顺序不能倒,因为不能用更新过的状态来更新(会让dp[v]多次被计算)
		dp[x][fa][k][0] += dp[v][fa][0][0];
		dp[x][fa][k][1] += dp[v][x][0][0];
		/*因为没有初始化dp,最先它自己一定为0
		如果不单独提出来处理,会保持0的状态 不被更新
		*/
		for(l = 0;l <= k;l++)
		{
		//这个l顺序也无所谓
			dp[x][fa][k][0] = Min(dp[x][fa][k][0],dp[v][fa][l][0] + dp[x][fa][k - l][0]);
			dp[x][fa][k][1] = Min(dp[x][fa][k][1],dp[v][x][l][0] + dp[x][fa][k - l][1]);	
		}
	}
}

还有一点得知道

第四维 表示的是当前结点是否有伐木场
在回溯后就没用了,因此需要进行合并,且还得将当前结点的木材运至方程中的fa
for(i = 1;i <= tot;i++)
{
//i的顺序无所谓
	int fa = ancestor[i];
	for(j = 0;j <= maxk;j++)
	{
	//j的顺序也无所谓
		if(j >= 1)
			dp[x][fa][j][0] = Min(dp[x][fa][j][0] + wood[x] * (dep[x] - dep[fa]),dp[x][fa][j - 1][1]);
		//单独处理是因为0 - 1 = -1 造成数组越界
		/*
		那为什么会有-1的操作了
		原题解说的挺清楚的
		先前算的dp[x][fa][k][1]是用dp[v][x][l][0] + dp[x][fa][k - l][1]来更新的
		而l + k - l = k < k + 1,但我们此时表示的是k + 1 的状态,故需-1
		也可以在上诉方程中加个-1
		改成dp[v][x][l][0] + dp[x][fa][k - l + 1][1]
		*/
		else dp[x][fa][j][0] += wood[x] * (dep[x] - dep[fa]);
		//先前的记录
	}
}

代码

#include <vector>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
  
inline void Read(int &x)
{
    x = 0;
    char a = getchar();
    bool f = 0;
    while(a < '0'||a > '9') {if(a == '-') f = 1;a = getchar();}
    while(a >= '0'&&a <= '9') {x = x * 10 + a - '0';a = getchar();}
    if(f) x *= -1;
}
const int MAXN = 202;
vector<int> G[MAXN],Road[MAXN];
int maxk,wood[MAXN],ancestor[MAXN],tot,dep[MAXN],dp[MAXN][MAXN][52][2];
bool vis[MAXN];
//dp[i][j][k]表示第i个结点最近且建了伐木场的为j 共建k个的最小花费 1 表示在i点建了 

template<typename T>
inline T Min(T a,T b) {if(a < b) return a;return b;}
inline void dfs(int x)
{
	int i,j,k,l;;
	ancestor[++tot] = x;
	for(i = 0;i < G[x].size();i++)
	{
		int v = G[x][i],w = Road[x][i];
		if(!vis[v])
		{
			vis[v] = 1;
			dep[v] = dep[x] + w; 
			dfs(v);
			for(j = tot;j >= 1;j--)
			{
				int fa = ancestor[j];
				for(k = maxk;k >= 0;k--)
				{
					dp[x][fa][k][0] += dp[v][fa][0][0];
					dp[x][fa][k][1] += dp[v][x][0][0];
					for(l = k;l >= 0;l--)
					{
						dp[x][fa][k][0] = Min(dp[x][fa][k][0],dp[v][fa][l][0] + dp[x][fa][k - l][0]);
						dp[x][fa][k][1] = Min(dp[x][fa][k][1],dp[v][x][l][0] + dp[x][fa][k - l][1]);	
					}
				}
			}
		}
	}
	for(i = 1;i <= tot;i++)
	{
		int fa = ancestor[i];
		for(j = maxk;j >= 0;j--)
		{
			if(j >= 1)
				dp[x][fa][j][0] = Min(dp[x][fa][j][0] + wood[x] * (dep[x] - dep[fa]),dp[x][fa][j - 1][1]);
			else dp[x][fa][j][0] += wood[x] * (dep[x] - dep[fa]);
		}
	}
	tot--;
}
int main()
{
	int n,i;
	Read(n),Read(maxk);
	for(i = 1;i <= n;i++)
	{
		int v,dis;
		Read(wood[i]),Read(v),Read(dis);
		G[i].push_back(v);
		G[v].push_back(i);
		Road[i].push_back(dis);
		Road[v].push_back(dis);
	}
	vis[0] = 1;
	dfs(0);
	printf("%d",dp[0][0][maxk][0]);
    return 0;
}
posted @ 2019-08-08 19:30  resftlmuttmotw  阅读(129)  评论(0编辑  收藏  举报