题解:[ABC294G] Distance Queries on a Tree

题目描述

给定一颗有 n n n 个节点的树,带边权,要进行 Q Q Q 次操作,操作有两种:

1 i w:将第 i i i 条边的边权改为 w w w。
2 u v:询问 u , v u,v u,v 两点的距离。

输入格式

第一行,一个正整数 n n n。

接下来 n − 1 n-1 n−1 行,每行三个数 u , v , w u,v,w u,v,w,表示一条树边。

接下来一个正整数 Q Q Q。

接下来 Q Q Q 行,每行三个数,描述一个询问,格式如上。

输出格式

对于每个 2 2 2 操作,输出一行一个数,表示该询问的答案。

说明/提示

1 ≤ n , Q ≤ 2 × 1 0 5 , 1 ≤ w i ≤ 1 0 9 1\le n,Q\le 2\times10^5,1\le w_i\le 10^9 1≤n,Q≤2×105,1≤wi≤109

样例 #1

样例输入 #1

复制代码
5
1 2 3
1 3 6
1 4 9
4 5 10
4
2 2 3
2 1 5
1 3 1
2 1 5

样例输出 #1

复制代码
9
19
11

样例 #2

样例输入 #2

复制代码
7
1 2 1000000000
2 3 1000000000
3 4 1000000000
4 5 1000000000
5 6 1000000000
6 7 1000000000
3
2 1 6
1 1 294967296
2 1 6

样例输出 #2

复制代码
5000000000
4294967296

样例 #3

样例输入 #3

复制代码
1
1
2 1 1

样例输出 #3

复制代码
0

样例 #4

样例输入 #4

复制代码
8
1 2 105
1 3 103
2 4 105
2 5 100
5 6 101
3 7 106
3 8 100
18
2 2 8
2 3 6
1 4 108
2 3 4
2 3 5
2 5 5
2 3 1
2 4 3
1 1 107
2 3 1
2 7 6
2 3 8
2 1 5
2 7 6
2 4 7
2 1 7
2 5 3
2 8 6

样例输出 #4

复制代码
308
409
313
316
0
103
313
103
525
100
215
525
421
209
318
519

提示

数据范围

  • 1\\leq\\ N\\leq\\ 2\\times10\^5
  • 1\\leq\\ u\\ _\\ i,v\\ _\\ i\\leq\\ N\\ (1\\leq\\ i\\leq\\ N-1)
  • 1\\leq\\ w\\ _\\ i\\leq\\ 10\^9\\ (1\\leq\\ i\\leq\\ N-1)
  • 1\\leq\\ Q\\leq\\ 2\\times10\^5

题解

本题用到了 LCA、DFS序、树状数组和差分。

前置知识参考文章:LCA讲解DFS序讲解树状数组讲解差分算法

这道题有两个需要解决的问题。

我们先看操作2:询问两点的距离。

要求两点的距离,可以将 1 1 1 作为根节点,初始化为有向树,初始化出每个节点到根节点的距离 d i s t dist dist,然后计算出两个节点的最近公共祖先节点 l c a lca lca,两点之间的距离等于 d i s t [ u ] + d i s t [ v ] − 2 ∗ d i s t [ l c a ] dist[u] + dist[v] - 2*dist[lca] dist[u]+dist[v]−2∗dist[lca],因为这道题的权重在边上,所以初始化的时候,对于一条边有两个端点,那么这个边的权重会添加到 深度更大的节点上,深度小的节点不需要添加该权重。

操作 1 1 1:将某条边的边权改成 w w w,试想,如果操作 2 2 2 按上面的做法,那操作 1 1 1 执行一次,操作 2 2 2 就得重新初始化一次,因为改动一条边会对整个 d i s t dist dist 产生影响。因此此时我们想到树状数组和差分。

我们可以对树进行一次搜索,得到一个 D F S DFS DFS 序,每个节点都有两个时间戳,一个是到达这个节点的时间戳 i n [ u ] in[u] in[u],另一个是离开这个节点的时间戳 o u t [ u ] out[u] out[u],以第一次到达该结点的时间戳作为节点编号进行树状数组操作。

详细可以参考代码注释。

code

cpp 复制代码
#include "bits/stdc++.h"
using namespace std;
#define int long long
const int N = 2e5+7;
struct Edge{
    int u, v, w, ne;
}es[N<<1];
int n, c[N], q, h[N], t, op, u, v, w, x, y, z, idx = 0, f[N][20], dep[N], in[N], out[N];
// dep[x] 表示 节点 x 的深度,根节点深度为 1
// f[i][j] : 节点 i 往上 2^j 的 父节点
// in[u]: 表示到达节点 u 的时间戳 
// out[u]: 表示离开节点 u 的时间戳
void add(int u, int v, int w) {
    es[++idx] = Edge{u, v, w, h[u]};
    h[u] = idx;
    return ;
}
// dfs: 初始化dep数组,进入时间戳数组in,和离开节点时间戳数组 out
void dfs(int u, int fa) {
    in[u] = ++t;
    dep[u] = dep[fa] + 1;
    f[u][0] = fa;
    for(int e = h[u]; e; e = es[e].ne) {
        int v = es[e].v;
        if(v != fa) dfs(v, u);
    }
    out[u] = t;  
}
int lca(int u, int v) {
    if(u == v) return u;
    if(dep[u] < dep[v]) swap(u, v);
    for(int i=19; i>=0; i--) {
        if(dep[f[u][i]] >= dep[v]) u = f[u][i];
    }
    if(u == v) return u;
    for(int i=19; i>=0; i--) {
        if(f[u][i] != f[v][i]) u = f[u][i], v = f[v][i];
    }
    return f[u][0];
}
int lowbit(int x) { return x&-x; }
// 树状数组 add 操作,给 原数组 x 位置加上 w
void add(int x, int w) {
    for(int i=x; i<=n; i+=lowbit(i)) { c[i] += w;}
    return;
}
// 求[1...x] 的前缀和 
int sum(int x) {
    int res = 0;
    while(x) {
        res += c[x];
        x -= lowbit(x);
    }
    return res;
}
void update(int e, int w) {
    int u = es[e].u, v = es[e].v;
    if(dep[u] < dep[v]) swap(u, v);  // u指向下面的节点
    add(in[u], -es[e].w + w);  // 因为是修改操作,比如原来是 w1, 修改为 w2,则相当于加上 -w1 + w2;
    add(out[u]+1, es[e].w - w); // 差分,给out[u]+1 往后减去,这样只影响 区间
    es[e].w = w;  // 修改边权值
    return;
}
int query(int u, int v) {
    int fa = lca(u, v);  // 求最近公共祖先
    return sum(in[u]) + sum(in[v]) - 2*sum(in[fa]);  // 两节点距离公式
}

signed main() {
    cin >> n;
    for(int i=1; i<n; i++) {
        cin >> x >> y >> z;
        add(x, y, z);
        add(y, x, z);
    }
    dfs(1, 0);  // 初始化 DFS 序,深度数组
    for(int j=1; j<20; j++) {  // 初始化 LCA 的 f 数组
        for(int i=1; i<=n; i++) {
            f[i][j] = f[f[i][j-1]][j-1];
        }
    }
    for(int i=1; i<=idx; i+=2) {  // 遍历所有边,因为是双向边,一条边只需要遍历一次,所以 i+=2
        int u = es[i].u, v = es[i].v;
        if(dep[u] < dep[v]) swap(u, v);  // u 指向更深的节点,因为边的权重相当于影响了下面的节点(深度大的节点),对上面节点没影响
        // 这里多想想,当前边的权重影响的是哪些节点?其实影响的是从 DFS 序中编号为 in[u], out[u] 的节点,所以基于差分思想
        // 只需要修改 in[u] 位置和 out[u]+1 位置,树状数组针对的是 每个节点的时间戳编号数组。
        add(in[u], es[i].w);  
        add(out[u] + 1, -es[i].w);  // 差分
    }
    cin >> q;
    while(q--) {
        cin >> x >> y >> z;
        if(x == 1) update(y*2, z);  // 因为是双向边,所以要找输入的第 y条边,等价于数组中的 y*2 条边
        else cout << query(y, z) << endl;
    }
    return 0; 
}
相关推荐
你们补药再卷啦18 分钟前
人工智能算法概览
人工智能·算法
cnxy18823 分钟前
围棋对弈Python程序开发完整指南:步骤3 - 气(Liberties)的计算算法设计
python·算法·深度优先
AndrewHZ33 分钟前
【图像处理基石】什么是光栅化?
图像处理·人工智能·算法·计算机视觉·3d·图形渲染·光栅化
小白菜又菜41 分钟前
Leetcode 944. Delete Columns to Make Sorted
算法·leetcode
我找到地球的支点啦1 小时前
Matlab系列(006) 一利用matlab保存txt文件和读取txt文件
开发语言·算法·matlab
Dev7z1 小时前
基于Matlab实现GRACE卫星重力数据的全球水储量变化估算与分析
人工智能·算法·matlab
爱喝热水的呀哈喽2 小时前
11题目汇总
算法
三斗米2 小时前
Transformer入门:一文读懂《Attention Is All You Need》
算法·架构
Swift社区3 小时前
LeetCode 458 - 可怜的小猪
算法·leetcode·职场和发展
AI科技星3 小时前
宇宙的像素:真空中一点如何编码无限星光
数据结构·人工智能·算法·机器学习·重构