洛谷题单指南-图论之树-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);
    }
}

 

posted @ 2025-03-11 17:02  hackerchef  阅读(35)  评论(0)    收藏  举报