题目
思路
整道题看起来有很多要点要考虑
最先想出的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;
}