洛谷题单指南-图论之树-P3976 [TJOI2015] 旅游
原题链接:https://www.luogu.com.cn/problem/P3976
题意解读:树上支持两种操作:1、将u->v路径上所有点权值增加一个数 2、查询u->v路径上两个点权值a-b差值最大值,要求路径上a在b之后。
解题思路:
通过树链剖分,可以将树上路径转换成线性序列。
对于第一个问题:将路径上所有点增加一个值,借助于线段树区间更新很容易解决。
对于第二个问题:需要将问题进一步转换,对于树上u->v的路径,分为两个方向:
- 第一个方向是u->lca(u,v)方向,在dfs序中是从大到小
- 第二个方向是lca(u,v)->v方向,在dfs序中是从小到大
线段树节点表示的区间l~r默认是序号从小到大,这里我们定义线段树节点维护的信息则要考虑双向,可以如下来定义线段树节点
struct Node
{
int l, r;
int maxx, minx; //区间最大值、最小值
int lmax, rmax; //lmax:从l->r方向能得到的最大答案 rmax:从r->l方向能得到的最大答案
int add; //懒标记,表示子树都增加的值
} tr[N * 4];
我们同时维护两个方向的最大答案即可
对于线段树维护的信息,需要考虑两个层面的合并:
- 在线段树构建以及修改查询中都会涉及到节点合并操作,这里合并的节点表示的方向都是同向
- 在树链剖分过程中,对一个方向的重链的结果进行合并,这里合并的节点表示的方向也是同向;最后还要针对u->lca(u,v)以及lca(u,v)->v两个方向上的结果进行合并,这里合并的节点表示的方向是反向
对于同向的节点,这样来合并:
void pushup(Node &root, Node &L, Node &R)
{
root.maxx = max(L.maxx, R.maxx);
root.minx = min(L.minx, R.minx);
root.lmax = max(L.lmax, R.lmax, R.maxx - L.minx);
root.rmax = max(L.rmax, R.rmax, L.maxx - R.minx);
}
maxx,minx的合并比较好理解,就是两个子节点的最大、最小值
lmax,rmax的合并可以类比最大子段和:要么是前一段的答案、要么是后一段的答案、要么用后一段的最大值-前一段的最小值,三者取最大即可。
对与反方向的节点,如果Node L表示序列从u->lca(u,v)的结果,Node R表示lca(u,v)->v的结果,要合并答案,需要先将L的lmax和rmax交换,再将L和R合并:
swap(L.lmax, L.rmax);
pushup(res, L, R);
具体流程请参考完整代码。
100分代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 50005, INF = 1e9;
vector<int> g[N];
int a[N];
int depth[N], fa[N], sz[N], son[N], dfn[N], cnt, rk[N], top[N];
struct Node
{
int l, r;
int maxx, minx; //区间最大值、最小值
int lmax, rmax; //lmax:从l->r方向能得到的最大答案 rmax:从r->l方向能得到的最大答案
int add; //lazy tag,子节点都增加的值
} tr[N * 4];
int n, q;
Node newNode()
{
return {0,0,-INF,INF,-INF,-INF};
}
void pushup(Node &root, Node &L, Node &R)
{
root.maxx = max(L.maxx, R.maxx);
root.minx = min(L.minx, R.minx);
root.lmax = max(max(L.lmax, R.lmax), R.maxx - L.minx);
root.rmax = max(max(L.rmax, R.rmax), L.maxx - R.minx);
}
void pushup(int u)
{
Node left = tr[u << 1];
Node right = tr[u << 1 | 1];
pushup(tr[u], left, right);
}
void addtag(int u, int val)
{
tr[u].maxx += val;
tr[u].minx += val;
tr[u].add += val;
}
void pushdown(int u)
{
if(tr[u].add)
{
addtag(u << 1, tr[u].add);
addtag(u << 1 | 1, tr[u].add);
tr[u].add = 0;
}
}
void build(int u, int l, int r)
{
tr[u] = {l, r};
if(l == r) tr[u].maxx = tr[u].minx = a[rk[l]];
else
{
int mid = l + r >> 1;
build(u << 1, l, mid);
build(u << 1 | 1, mid + 1, r);
pushup(u);
}
}
Node query(int u, int l, int r)
{
if(tr[u].l >= l && tr[u].r <= r)
{
return tr[u];
}
else if(tr[u].l > r || tr[u].r < l) return newNode();
else
{
pushdown(u);
Node res;
Node left = query(u << 1, l, r);
Node right = query(u << 1 | 1, l, r);
pushup(res, left, right);
return res;
}
}
void update(int u, int l, int r, int val)
{
if(tr[u].l >= l && tr[u].r <= r) addtag(u, val);
else if(tr[u].l > r || tr[u].r < l) return;
else
{
pushdown(u);
update(u << 1, l, r, val);
update(u << 1 | 1, l, r, val);
pushup(u);
}
}
void dfs1(int u, int p, int d)
{
sz[u] = 1;
depth[u] = d;
fa[u] = p;
for(auto v : g[u])
{
if(v == p) continue;
dfs1(v, u, d + 1);
sz[u] += sz[v];
if(sz[v] > sz[son[u]]) son[u] = v;
}
}
void dfs2(int u, int t)
{
top[u] = t;
dfn[u] = ++cnt;
rk[cnt] = u;
if(son[u]) dfs2(son[u], t);
for(auto v : g[u])
{
if(v == fa[u] || v == son[u]) continue;
dfs2(v, v);
}
}
int queryPath(int u, int v)
{
Node L = newNode(); //u->lca(u,v)的答案
Node R = newNode(); //lca(u,v)->v的答案
Node res;
while(top[u] != top[v])
{
if(depth[top[u]] < depth[top[v]])
{
res = query(1, dfn[top[v]], dfn[v]);
Node t = R;
pushup(R, res, t); //顺序不能弄反,res在路径中更靠上,序号更小,因此是左边
v = fa[top[v]];
}
else
{
res = query(1, dfn[top[u]], dfn[u]);
Node t = L;
pushup(L, res, t);
u = fa[top[u]];
}
}
if(depth[u] > depth[v])
{
res = query(1, dfn[v], dfn[u]);
Node t = L;
pushup(L, res, t);
}
else
{
res = query(1, dfn[u], dfn[v]);
Node t = R;
pushup(R, res, t);
}
swap(L.lmax, L.rmax);
pushup(res, L, R);
return res.lmax;
}
void updatePath(int u, int v, int val)
{
while(top[u] != top[v])
{
if(depth[top[u]] < depth[top[v]]) swap(u, v);
update(1, dfn[top[u]], dfn[u], val);
u = fa[top[u]];
}
if(depth[u] > depth[v]) swap(u, v);
update(1, dfn[u], dfn[v], val);
}
int main()
{
cin >> n;
for(int i = 1; i <= n; i++) cin >> a[i];
int u, v, val;
for(int i = 1; i < n; i++)
{
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
dfs1(1, 0, 0);
dfs2(1, 1);
build(1, 1, n);
cin >> q;
while (q--)
{
cin >> u >> v >> val;
cout << queryPath(u, v) << endl;
updatePath(u, v, val);
}
}
浙公网安备 33010602011771号