树上点差分

树上点差分

https://www.luogu.com.cn/problem/P3128

题意理解

  • 一棵树,根节点为1

  • 大量修改操作,但是每次是将两个点之间的路径上的所有点,都增加1

  • 一个查询操作,问最后哪一个点上的值最大。

算法解析

使用树上差分+最近公共祖先即可。

就是一个点上的最后权值,就是统计子树和的过程。

树上差分

举个例子:有一个树。设原树如下,现要将2,3之间路径上的所有点的权值增加3,设原权值均为0。可以求任一个点的权值。

现在有两个想法:

  • 暴力:一个是将2,3路径上的所有权值,都+3
  • 差分:一个是对路径上的重要节点进行修改(而不是暴力全改),作为其差分数组的值,最后在求值时,利用dfs遍历求出差分数组的前缀和。一个点上的最后权值,就是统计子树和的过程。

我们现在来看看第二种方式:

树上点差分

设将两点u,v之间路径上的所有点权增加x,o=LCA(u,v),o的父亲节点为p,则操作如下:

diff[u]+=x,diff[v]+=x,diff[o]-=x,diff[p]-=x;

如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3LmSppwB-1594570824877)(树上差分.assets/1594556591414.png)]

差分后:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B3vovchx-1594570824882)(树上差分.assets/1594557016139.png)]

此时,我们可以求任意节点的权值。

任意节点的权值 = 自己和其子树的差分权值之和。

例如:4号节点 = -3+ 3 + 3 =3

3号节点 = 3

5号节点 = 0。

code

#include <bits/stdc++.h>
using namespace std;

const int N = 5e4+10;
int n,q;
int h[N],e[N*2],ne[N*2],idx;
int fa[N][22];
int deep[N];//记录深度
int lg[N];
int p[N];//差分数组
void add(int x, int y){
    e[idx] = y; ne[idx] = h[x]; h[x] = idx++;
}
//填写上fa数组
void dfs1(int now , int fath){//now表示当前节点,fath表示它的父亲节点
    fa[now][0] = fath; deep[now] = deep[fath] +1;
    for (int i = 1;  i <= lg[deep[now]]; ++i) {
        //根据递推公式 fa(i,j) = fa(fa(i,j-1),fa(i,j-1))
        fa[now][i] = fa[fa[now][i-1]][i-1];
    }
    for (int i = h[now]; i != -1 ; i = ne[i]) {
        int j = e[i]; // now的所指向的节点
        if( j != fath) dfs1(j,now); //如果所指节点不是父节点,那么遍历子节点
    }
}
//求最短公共祖先
int lca(int x, int y){
    if(deep[x] < deep[y]) swap(x,y); //保证x的深度是最大的
    // x,y争取跳到同一层
    while (deep[x] > deep[y]) x = fa[x][lg[deep[x] - deep[y]]];
    if(x == y) // 如果其中一个节点是另一个父节点,那么直接返回
        return x;
    for (int i = lg[deep[x]]; i >=0 ; --i) { // x,y现在在同一层了,那么一起跳
        if(fa[x][i] != fa[y][i]) x = fa[x][i],y = fa[y][i]; //一直调到公共祖先下一层
    }
    return  fa[x][0];
}
void dfs2(int u, int fath) {
    for (int i = h[u]; i != -1; i = ne[i]) {
        int j = e[i];
        if(j !=  fath){
            dfs2(j,u);
            p[u] += p[j];//累计权值
        }
    }
}
int main(){
    memset(h,-1, sizeof(h));
    //输入
    cin >> n >> q;
    //建图
    for (int i = 0; i < n-1; ++i) {
        int x,y;
        cin >> x >> y;
        add(x,y);add(y,x);
    }
    //给lg打表
    lg[0] = -1;
    for (int i = 1; i <= n ; ++i) {
        lg[i] = lg[i>>1] + 1;
    }
    dfs1(1,0);
    while(q--){
        int a, b;
        cin >> a >> b;
        //输出下lca的结果
//        cout << "lca:"<< lca(a,b) << endl;
        int lc = lca(a,b);
        p[lc]--;
        p[fa[lc][0]]--;
        p[a]++;
        p[b]++;
    }
    dfs2(1,0);//累计权值,完成差分数组
    int res = 0;
    for (int i = 1; i <= n ; ++i) {
        res = max(res,p[i]);
    }
    cout << res;
    return 0;
}
### 树上子树差分算法实现及应用场景 树上子树差分是一种在树结构中进行操作的算法,主要用于对树的子树进行高效的修改或查询。它通常结合树的遍历顺序(如欧拉序)和线段树、树状数组等数据结构来实现。 #### 1. 算法核心思想 树上子树差分的核心思想是将树上的点映射到一个序列上,使得子树的操作可以转化为区间操作。常见的方法是利用 **欧拉序** 或 **DFS 序** 将树上的节点映射到一维数组中。这样,对某个节点的子树进行操作就可以转化为对该节点对应的区间进行操作[^4]。 #### 2. 实现步骤 以下是树上子树差分的实现步骤: - **构建 DFS 序**:通过深度优先搜索(DFS),记录每个节点的进入时间和退出时间。进入时间为该节点第一次被访问的时间戳,退出时间为其子树完全访问完毕后的时间戳。 - **映射到区间**:对于节点 $ u $,其子树对应于 DFS 序中的区间 $[in[u], out[u]]$,其中 $ in[u] $ 是节点 $ u $ 的进入时间,$ out[u] $ 是退出时间减一。 - **使用辅助数据结构**:利用线段树或树状数组对区间进行高效的操作(如加法、求和等)。 #### 3. 示例代码 以下是一个基于 DFS 序和树状数组的树上子树差分实现示例: ```python # 树状数组实现 class FenwickTree: def __init__(self, n): self.size = n self.tree = [0] * (n + 1) def update(self, index, value): while index <= self.size: self.tree[index] += value index += index & -index def query(self, index): result = 0 while index > 0: result += self.tree[index] index -= index & -index return result # 树上子树差分 def subtree_difference(n, edges, queries): # 构建邻接表 adj = [[] for _ in range(n + 1)] for u, v in edges: adj[u].append(v) adj[v].append(u) # 记录 DFS 序 enter_time = [0] * (n + 1) exit_time = [0] * (n + 1) timer = 0 def dfs(u, parent): nonlocal timer enter_time[u] = timer timer += 1 for v in adj[u]: if v != parent: dfs(v, u) exit_time[u] = timer dfs(1, -1) # 假设节点从 1 开始编号 # 初始化树状数组 ft = FenwickTree(timer) # 处理查询 results = [] for node, delta in queries: ft.update(enter_time[node], delta) ft.update(exit_time[node], -delta) # 查询所有节点的结果 for i in range(1, n + 1): results.append(ft.query(enter_time[i])) return results # 示例输入 n = 5 edges = [(1, 2), (1, 3), (2, 4), (2, 5)] queries = [(1, 10), (2, 5), (3, -3)] # 输出结果 print(subtree_difference(n, edges, queries)) ``` #### 4. 应用场景 树上子树差分的应用场景包括但不限于以下几种: - **资源分配问题**:在树形结构的网络中分配资源,例如计算每个子树的总流量或负载。 - **动态统计**:实时更新树上某些节点的状态,并快速查询子树的统计信息(如和、最大值、最小值等)。 - **路径查询与修改**:结合 LCA(最近公共祖先)算法,可以在树上进行路径的差分操作。 #### 5. 注意事项 - 在实际应用中,可能需要根据具体问题调整差分的范围或方式。 - 如果树的规模较大,建议使用高效的区间操作数据结构(如线段树或树状数组)以优化性能。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值