【Luogu P4315】月下“毛景树”(树链剖分,线段树)

本文介绍了一种处理树形结构数据的问题,涉及树链剖分、线段树等数据结构。通过实例展示了如何使用这些算法来解决毛毛虫在树上寻找最多毛毛果的查询和修改操作。在实现过程中,注意了区间覆盖和单点修改的细节,以及懒惰标记的处理。代码中包含了树的深度计算、节点大小计算、拓扑排序和线段树的构建与更新。

题目

月下“毛景树”

题目描述

毛毛虫经过及时的变形,最终逃过的一劫,离开了菜妈的菜园。 毛毛虫经过千山万水,历尽千辛万苦,最后来到了小小的绍兴一中的校园里。

爬啊爬~爬啊爬 毛毛虫爬到了一颗小小的“毛景树”下面,发现树上长着他最爱吃的毛毛果 “毛景树”上有N个节点和N-1条树枝,但节点上是没有毛毛果的,毛毛果都是长在树枝上的。但是这棵“毛景树”有着神奇的魔力,他能改变树枝上毛毛果的个数:

  • Change k w:将第k条树枝上毛毛果的个数改变为w个。

  • Cover u v w:将节点u与节点v之间的树枝上毛毛果的个数都改变为w个。

  • Add u v w:将节点u与节点v之间的树枝上毛毛果的个数都增加w个。 由于毛毛虫很贪,于是他会有如下询问:

  • Max u v:询问节点u与节点v之间树枝上毛毛果个数最多有多少个。

输入格式

第一行一个正整数N。

接下来N-1行,每行三个正整数Ui,Vi和Wi,第i+1行描述第i条树枝。表示第i条树枝连接节点Ui和节点Vi,树枝上有Wi个毛毛果。 接下来是操作和询问,以“Stop”结束。

输出格式

对于毛毛虫的每个询问操作,输出一个答案。

样例 #1

样例输入 #1

4
1 2 8
1 3 7
3 4 9
Max 2 4
Cover 2 4 5
Add 1 4 10
Change 1 16
Max 2 4
Stop

样例输出 #1

9
16

提示

1<=N<=100,000,操作+询问数目不超过100,000。

保证在任意时刻,所有树枝上毛毛果的个数都不会超过10^9个。

思路

树链剖分+线段树。
此题维护边权和,只需将边权转化成点权即可。
对于每条边,我们将其权值转化为深度较深的点的点权,这样可以一一对应。
区间查询是,两点的 LCA 的点权是不可以取的,它所对应的边并不在两点路径上。
所以树链剖分的最后一次操作,区间应为 i d [ x ] + 1 ∼ i d [ y ] id[x]+1 \sim id[y] id[x]+1id[y]
接下来就是码码码了。
要考虑清楚懒标记的下传。区间覆盖的懒标记优先级更高。如果区间覆盖要下传,左右儿子区间加的懒标记就要被清零了!

然后又调了好长时间,结果发现单点修改时下标搞错了,应该是树剖后的 i d [ x ] id[x] id[x],而非 x x x

代码

#include <bits/stdc++.h>
#define rep(i, a, b) for (register int i(a); i <= b; ++i)
#define per(i, a, b) for (register int i(a); i >= b; --i)
#define FILE(s) freopen(s".in", "r", stdin), freopen(s".out", "w", stdout)
#define mem(a, x) memset(a, x, sizeof a)
#define pb push_back
#define umap unordered_map
#define pqueue priority_queue
#define mp make_pair
#define PI acos(-1)
//#define int long long

using namespace std;
typedef long long ll;
typedef unsigned long long ull;
int n, dep[100005], siz[100005], son[100005], top[100005], father[100005], a[100005], id[100005], w[100005], tot;
vector <int> e[100005];
struct enode {
    int x, y, z;
} ed[100005];

template <typename _T>
void rd(_T &x) {
    int f = 1; x = 0;
    char s = getchar();
    while (s > '9' || s < '0') {if (s == '-') f = -1; s = getchar();}
    while (s >= '0' && s <= '9') x = (x<<3)+(x<<1)+(s^48), s = getchar();
    x *= f;
}

template <typename _T>
void write(_T x) {
    if (x < 0) putchar('-'), x = -x;
    if (x > 9) write(x/10);
    putchar(x%10+'0');
    return ;
}

struct seg {
    int tr[400005], tag[400005], tag2[400005];

    #define ls k<<1
    #define rs k<<1|1

    inline void pushup(int k) {
        tr[k] = max(tr[ls], tr[rs]);
        return ;
    }

    inline void pushdown(int k) {
        int x = tag2[k];
        if (x >= 0) tag2[ls] = tag2[rs] = x, tr[ls] = tr[rs] = x, tag[ls] = tag[rs] = 0;
        tag2[k] = -1;
        x = tag[k];
        tag[k] = 0;
        tag[ls] += x, tag[rs] += x;
        tr[ls] += x, tr[rs] += x;
        return ;
    }

    void build(int k, int l, int r) {
        tag2[k] = -1;
        if (l == r) {
            tr[k] = w[l];
            return ;
        }
        int mid = (l+r) >> 1;
        build(ls, l, mid); build(rs, mid+1, r);
        pushup(k);
        return ;
    }

    void modify_p(int k, int l, int r, int pos, int c) {
        if (l == r) {
            tr[k] = c;
            return ;
        }
        pushdown(k);
        int mid = (l+r) >> 1;
        if (pos <= mid) modify_p(ls, l, mid, pos, c);
        else modify_p(rs, mid+1, r, pos, c);
        pushup(k);
        return ;
    }
    
    void modify(int k, int l, int r, int L, int R, int c) {
        if (L > R) return ;
        if (l >= L && r <= R) {
            tr[k] = c;
            tag2[k] = c;
            tag[k] = 0;
            return ;
        }
        pushdown(k);
        int mid = (l+r) >> 1;
        if (L <= mid) modify(ls, l, mid, L, R, c);
        if (R > mid) modify(rs, mid+1, r, L, R, c);
        pushup(k);
        return ;
    }

    void add(int k, int l, int r, int L, int R, int c) {
        if (L > R) return ;
        if (l >= L && r <= R) {
            tr[k] += c;
            tag[k] += c;
            return ;
        }
        pushdown(k);
        int mid = (l+r) >> 1;
        if (L <= mid) add(ls, l, mid, L, R, c);
        if (R > mid) add(rs, mid+1, r, L, R, c);
        pushup(k);
        return ;
    }

    int query(int k, int l, int r, int L, int R) {
        if (L > R) return -1;
        if (l >= L && r <= R) return tr[k];
        pushdown(k);
        int mid = (l+r) >> 1, ret = 0;
        if (L <= mid) ret = max(ret, query(ls, l, mid, L, R));
        if (R > mid) ret = max(ret, query(rs, mid+1, r, L, R));
        return ret;
    }
} Tr;

void dfs1(int x, int fa) {
    dep[x] = dep[fa]+1;
    father[x] = fa;
    siz[x] = 1;
    int maxn = 0;
    for (auto y : e[x]) {
        if (y == fa) continue;
        dfs1(y, x);
        siz[x] += siz[y];
        if (maxn < siz[y]) maxn = siz[y], son[x] = y;
    }
    return ;
}

void dfs2(int x, int fa, int tp) {
    top[x] = tp;
    id[x] = ++tot;
    w[tot] = a[x];
    if (son[x] == 0) return ;
    dfs2(son[x], x, tp);
    for (auto y : e[x]) {
        if (y == fa || y == son[x]) continue;
        dfs2(y, x, y);
    }
    return ;
}

int query_road(int x, int y) {
    int ret = 0;
    while (top[x] != top[y]) {
        if (dep[top[x]] < dep[top[y]]) x ^= y ^= x ^= y;
        ret = max(ret, Tr.query(1, 1, n, id[top[x]], id[x]));
        x = father[top[x]];
    }
    if (dep[x] > dep[y]) x ^= y ^= x ^= y;
    ret = max(ret, Tr.query(1, 1, n, id[x]+1, id[y]));
    return ret;
}

void cover_road(int x, int y, int z) {
    while (top[x] != top[y]) {
        if (dep[top[x]] < dep[top[y]]) x ^= y ^= x ^= y;
        Tr.modify(1, 1, n, id[top[x]], id[x], z);
        x = father[top[x]];
    }
    if (dep[x] > dep[y]) x ^= y ^= x ^= y;
    Tr.modify(1, 1, n, id[x]+1, id[y], z);
    return ;
}

void modify_road(int x, int y, int z) {
    while (top[x] != top[y]) {
        if (dep[top[x]] < dep[top[y]]) x ^= y ^= x ^= y;
        Tr.add(1, 1, n, id[top[x]], id[x], z);
        x = father[top[x]];
    }
    if (dep[x] > dep[y]) x ^= y ^= x ^= y;
    Tr.add(1, 1, n, id[x]+1, id[y], z);
    return ;
}

int main() {
    rd(n);
    rep(i, 1, n-1) {
        int x, y, z; rd(x), rd(y), rd(z);
        e[x].pb(y), e[y].pb(x);
        ed[i].x = x, ed[i].y = y, ed[i].z = z;
    }
    dfs1(1, 0);
    rep(i, 1, n-1) {
        if (ed[i].x == father[ed[i].y]) a[ed[i].y] = ed[i].z;
        else a[ed[i].x] = ed[i].z;
    }
    dfs2(1, 0, 1);
    Tr.build(1, 1, n);
    while (true) {
        char s[11]; scanf("%s", s);
        if (s[0] == 'S') break;
        if (s[0] == 'M') {
            int u, v; rd(u), rd(v);
            printf("%d\n", query_road(u, v));
        } else if (s[0] == 'C' && s[1] == 'o') {
            int u, v, w; rd(u), rd(v), rd(w);
            cover_road(u, v, w);
        } else if (s[0] == 'C' && s[1] == 'h') {
            int u, w; rd(u), rd(w);
            int x;
            if (ed[u].x == father[ed[u].y]) x = ed[u].y;
            else x = ed[u].x;
            Tr.modify_p(1, 1, n, id[x], w);
        } else {
            int u, v, w; rd(u), rd(v), rd(w);
            modify_road(u, v, w);
        }
    }
    return 0;
}
### 树的直径问题概述 树的直径是指树中最长的简单路径,通常定义为两个节点之间的最大距离。解决树的直径问题的方法主要包括动态规划和贪心算法两种思路。 #### 动态规划方法 动态规划方法中,可以通过两次深度优先搜索(DFS)来求解树的直径。具体步骤如下: 1. 从任意一个节点出发进行一次 DFS,找到距离该节点最远的节点 $ u $。 2. 从节点 $ u $ 再次进行一次 DFS,找到距离 $ u $ 最远的节点 $ v $,路径 $ u \rightarrow v $ 即为树的直径。 在实现过程中,可以维护两个数组 `dp` 和 `dp2`,分别表示从某个节点出发的最长路径和次长路径。通过更新这两个数组,可以计算出经过每个节点的最长路径,并最终找到整个树的最长路径。 ```cpp void dfs(int u, int fa) { for (auto x : g[u]) { if (x == fa) continue; dfs(x, u); f[u] = max(f[u], d[u] + d[x] + 1); d[u] = max(d[u], d[x] + 1); } } ``` #### 贪心方法 贪心方法的核心思想是通过两次 DFS 找到树的直径。第一次 DFS 用于找到距离任意起点最远的节点 $ u $,第二次 DFS 则从 $ u $ 出发找到最远的节点 $ v $。路径 $ u \rightarrow v $ 即为树的最长路径。 这种方法的时间复杂度为 $ O(n) $,适用于大多数树的直径问题。 #### 洛谷 P1099 树网的核问题 在洛谷 P1099 [NOIP2007 提高组] 树网的核问题中,树的直径是核心概念之一。题目要求找到树中的一条路径,使得该路径的长度不超过给定值,并且尽可能多地覆盖树中的节点。树的直径在该问题中起到了关键作用,通常需要结合枚举和树的直径特性进行求解。 #### 树的最长路径算法 树的最长路径算法通常包括以下步骤: 1. **选择起点**:从任意一个节点开始进行 DFS。 2. **寻找最远节点**:通过 DFS 找到距离起点最远的节点 $ u $。 3. **再次寻找最远节点**:从 $ u $ 开始进行第二次 DFS,找到距离 $ u $ 最远的节点 $ v $。 4. **计算直径**:路径 $ u \rightarrow v $ 即为树的直径。 该算法的时间复杂度为 $ O(n) $,适用于大多数树的直径问题。 ### 相关问题 1. 如何通过两次 DFS 找到树的直径? 2. 树的直径问题中的动态规划方法是如何实现的? 3. 洛谷 P1099 树网的核问题中如何应用树的直径特性? 4. 树的最长路径算法的时间复杂度是多少? 5. 如何通过贪心算法解决树的直径问题?
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值