树形DP 尽量详细,
题意是每个城市都有一个Wi 油的数量,给出了一些道路将N个城市形成一个无根树,每条道路有不同的耗油量
从任意一个城市x 到 任意一个城市y ,在起始点 x 直接加 Wx 的汽油,到 y 城市最多能有多少汽油剩余量。
之前遇见无根树题目总是发懵,因为当dfs查找的时候,这个点不是查找路径的上游点,就不会对以它为转折点的权值更新。
在求 树的直径 问题时规避了这个问题,也就没有多想,直到遇见此类题目,才开始想解决问题。
题目:CodeForces - 1084D
那么现在就讨论和解决这个问题
首先我们假设这个树是只有一条路径的树(类似这种),我们将无根树转化为有根树(就是随便找一个点做根,当前,我们定位最左边那个点 为 树根)
- 其实向下dfs的时候,我们可以更新的是 这个节点 到 所有的子节点的时候 的最大剩余油量(可以想到 当其叶子节点时,他向下没有了,那么最大值就是他自己)。
- 那么 每次返回上一层的时候,将这个节点以及它所有子节点视为一个子图,在这个子图里的所有x -> y,就都被找到了 因为我们每次更新,都是同步和 ans 取一个max的。
这个时候我们将情况变复杂,在某个节点,出现了分支结构(而且可能超过两条分支,我们要考虑普遍结构,并不是全为二叉结构)
我们将要更新红色点,他有四个子节点,我们要计算的就是 当它作为连接某两个点 的转折点 时怎么计算
注意:如果红色点作为转折点 连接某两点 这条线路 确实是最终结果,那么就可以更新到,如果不是 也不影响最终结果
我们只需要更新,这个点 最大子节点,次大子节点 到 它本身 的sum 就好了
有两种写法,但其实是一种方式,我们先看一个简单易懂的,我直接截屏其他大佬的这一小部分代码
红色框是维护 最大子节点 和 次大子节点
蓝色框是维护直线式最值
绿色框就是 (本身价值 + 最大子节点价值 + 次大子节点价值)
那么我们来看另一个大神的精简版
绿色 是还是维护直线式的最值
白色 的部分就是 Dfs的时候 更新 最值的,当所有子节点都遍历过了,最大值和次大值必定进行了一次sum(重点)
贴上大神代码(代码末端有大神原文链接,那个第一版本,就是DfS和建图方式不一样,其他的都一样,就不贴了)
还有一个重点,因为这个 转折点 更新到 ans 的过程在父节点的dfs层中 ,所以初始点1的父节点设为 0,另一个版本 1 的父节点还是 1 。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=3e5+10;
#define pb push_back
#define mp make_pair
vector<pair<ll,ll> > v[N];
int val[N],n;
ll dp[N];
ll ans;
void dfs(int u,int fa)
{
dp[u]=val[u];
ans=max(ans,dp[u]);
for(int i=0;i<v[u].size();i++)
{
int to=v[u][i].first;
if(to==fa) continue;
dfs(to,u);
ans=max(ans,dp[to]+dp[u]-v[u][i].second);
dp[u]=max(dp[u],val[u]+dp[to]-v[u][i].second);
}
}
int main()
{
int x,y,z;
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&val[i]);
for(int i=1;i<n;i++)
{
scanf("%d%d%d",&x,&y,&z);
v[x].pb(mp(y,z));
v[y].pb(mp(x,z));
}
dfs(1,0);
printf("%lld\n",ans);
return 0;
}
---------------------
作者:mmk27
来源:CSDN
原文:https://blog.csdn.net/mmk27_word/article/details/85001714