题解 P3976 [TJOI2015] 旅游
题意
有一棵 $n$ 个节点的树,每个节点有一个权值。
有 $q$ 此操作,每次操作由 $u$ 沿最短路径到达 $v$,求 $val_i-val_j$ 的最大值,($i$,$j$ 位于此最短路径上,且先经过 $i$,再经过 $j$),经过后,此路径上所有点的价值上升 $v$。
分析
作为一道树形并且是最短路径的题,我们当然先从序列上思考。
显然是用线段树来维护,重点又变成了区间上的合并。
先思考从左到右,我们合并后的总区间的答案,只有三种可能:
- 左区间买,左区间卖。
- 左区间买,右区间卖。
- 右区间买,右区间卖。
对于第一种与第三种情况,我们可以直接从左区间与右区间继承,作为第二种,显然是从左区间的最小值买,右区间的最大值卖,之后就可以维护好我们的区间合并。由于不止左至右,我们同时需要维护右到左。
我们使用结构体来维护我们的这个区间的左至右的答案,右至左的答案,区间最大值,区间最小值。
struct node {
int mx,mn,c1,c2;//c1 左至右 c2 右至左
inline friend node operator + (node fi,node se) {
node res;
res.mx=max(fi.mx,se.mx);
res.mn=min(fi.mn,se.mn);
res.c1=max(max(fi.c1,se.c1),se.mx-fi.mn);
res.c2=max(max(fi.c2,se.c2),fi.mx-se.mn);
return res;
}
}
维护好了区间,我们自然解决了序列上的该问题,至于放在树上,我们自然可以用树链剖分来解决。
在维护树上答案时,我们的左端点时在祖先,因此我们两条链的合并还需要重新解决。
dfs(1,0);
redfs(1,1);
tree.build(1,1,n);
int Q=read();
while(Q--) {
int u=read(),v=read(),x=read();
int lca=LCA(u,v);
if(lca==u) {
node res=query(v,lca);
cout<<res.c1<<"\n";
} else if(lca==v) {
node res=query(u,lca);
cout<<res.c2<<"\n";
} else {
node l=query(u,lca),r=query(v,lca);
cout<<max(max(l.c2,r.c1),r.mx-l.mn)<<"\n";
}
change(u,v,x);
}

浙公网安备 33010602011771号