解题思路转自:https://blog.csdn.net/castomere/article/details/86607780
题目链接:https://vjudge.net/problem/CodeForces-1084D
题目大意:题意是每个城市都有一个Wi 油的数量,给出了一些道路将N个城市形成一个无根树,每条道路有不同的耗油量,
从任意一个城市x 到 任意一个城市y ,在起始点 x 直接加 Wx 的汽油,到 y 城市最多能有多少汽油剩余量。
之前遇见无根树题目总是发懵,因为当dfs查找的时候,这个点不是查找路径的上游点,就不会对以它为转折点的权值更新。
在求 树的直径 问题时规避了这个问题,也就没有多想,直到遇见此类题目,才开始想解决问题。
那么现在就讨论和解决这个问题
首先我们假设这个树是只有一条路径的树(类似这种),我们将无根树转化为有根树(就是随便找一个点做根,当前,我们定位最左边那个点 为 树根)
1.其实向下dfs的时候,我们可以更新的是 这个节点 到 所有的子节点的时候 的最大剩余油量(可以想到 当其叶子节点时,他向下 没有了,那么最大值就是他自己)。
2. 那么 每次返回上一层的时候,将这个节点以及它所有子节点视为一个子图,在这个子图里的所有x -> y,就都被找到了 因为我们每次更新,都是同步和 ans 取一个max的。
这个时候我们将情况变复杂,在某个节点,出现了分支结构(而且可能超过两条分支,我们要考虑普遍结构,并不是全为二叉结构)。
我们将要更新红色点,他有四个子节点,我们要计算的就是 当它作为连接某两个点 的转折点 时怎么计算
注意:如果红色点作为转折点 连接某两点 这条线路 确实是最终结果,那么就可以更新到,如果不是 也不影响最终结果
我们只需要更新,这个点 最大子节点,次大子节点 到 它本身 的sum 就好了
有两种写法,但其实是一种方式,我们先看一个简单易懂的,我直接截屏其他大佬的这一小部分代码
红色框是维护 最大子节点 和 次大子节点
蓝色框是维护直线式最值
绿色框就是 (本身价值 + 最大子节点价值 + 次大子节点价值)
那么我们来看另一个大神的精简版
绿色 是还是维护直线式的最值
白色 的部分就是 Dfs的时候 更新 最值的,当所有子节点都遍历过了,最大值和次大值必定进行了一次sum(重点)
附上我的AC代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#include<stack>
#include<map>
#include<set>
using namespace std;
typedef long long ll;
int cnt=0,n;
const int INF=3e5+5;
int head[INF];
struct node{
int d;
int di;
int next;
}f[INF*2];
int a[INF];
ll ans,dp[INF];//dp记录的是从根节点到每个节点的最大距离。
int vis[INF];
void init()
{
int i,j;
for(i=0;i<=n;i++)
head[i]=-1;
cnt=0;
}
void add(int x,int y,int z)
{
f[cnt].d =z;
f[cnt].di =y;
f[cnt].next =head[x];
head[x]=cnt++;
}
void dfs(int x)
{
vis[x]=1;
int i,j,k;
dp[x]=a[x];
ans=max(ans,dp[x]);
for(i=head[x];i!=-1;i=f[i].next )
{
j=f[i].di ;
k=f[i].d ;
if(vis[j]==0)
{
dfs(j);
ans=max(ans,dp[x]+dp[j]-k);//这样处理是因为,所求的路径可能不包含根节点,ans表示的是最大值
dp[x]=max(dp[x],a[x]+dp[j]-k);
}
}
}
int main()
{
scanf("%d",&n);
int i,j,k,val,l;
init();
for(i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
for(i=1;i<n;i++)
{
scanf("%d%d%d",&j,&k,&l);
add(j,k,l);add(k,j,l);
}
memset(vis,0,sizeof(vis));
ans=0;
dfs(1);
printf("%lld\n",ans);
return 0;
}