LCA和树上差分【2024蓝桥杯0基础】-学习笔记

LCA

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

算法步骤

求解LCA(x,y),假定deep[x]> deep[y]:
利用倍增法将x往上走直至deep[x]=deep[y]
枚举i从大到小,x往上走2’步到p,如果deep[p]<deep[x]则不走,否则走。
此时deep[x]=-deep[y]:
如果x=y,则答案为x,否则一起往上走枚举;从大到小,x往上走2’i步到px,y往上走2’i步到py,如果px=py,虽然是公共祖先,但无法保证最近公共祖先,因此不能走:
如果px != py时能走,最终走到第一个非公共祖先的点的父亲节点就是lca

代码复现

def lca(x, y):
    #保证x更深
    if deep[x] < deep[y]:
        x, y = y, x
    #利用倍增的方法往上走,使得deep[x] =deep[y]
    #从大到小枚举2^i步
    #如果走了这一步到达的点p[x][i]的深度仍然大于等于deep[y]则可以走
    for i in range(20, -1, -1):
        if deep[p[x][i]] >= deep[y]:
            x = p[x][i]

    #此时deep[x] = deep[y]
    if x == y:
        return x

    #一起往上走,如果2^i步,两个点的公共祖先相同则不能走,否则可以走
    for i in range(20, -1, -1):
        if p[x][i] != p[y][i]:
            x,y = p[x][i],p[y][i]
    #当走到最近的不同公共祖先的,上一个节点就是LCA
    return p[x][0]

def dfs(u, fa):
    #预处理
    deep[u] = deep[fa]+1
    p[u][0] = fa
    for i in range(1, 21):
        p[u][i] = p[p[u][i - 1]][i - 1]
    for v in G[u]:
        if v == fa:
            continue
        dfs(v, u)

import sys
sys.setrecursionlimit(10000)
input = sys.stdin.readline
n = int(input())
G = [[]for i in range(n + 1)]
#deep[u]表示u的深度
deep = [0]*(n + 1)
#p[u][i] 表示点u 往上走2^i到达的点
p = [[0]*21 for i in range(n + 1)]
for _ in range(n -1):
    u, v = map(int,input().split())
    G[u].append(v)
    G[v].append(u)
dfs(1, 0)
Q = int(input())
for _ in range(Q):
    x, y = map(int,input().split())
    print(lca(x, y))

树上差分

原数列的前缀和的差分数组还是原数列,原数列的差分数组的前缀和也是原数列,这就是差分被称为前缀和的逆运算的原因,也是差分可以用来优化操作的原因。实际上,差分优化和前缀和优化原理类似,只是实现相反。前级和优化常常对前缀和数组作差,差分优化也常常对差分数组求前缀和。
树上差分有什么作用?
如果题目要求对树上的一段路径进行操作,并询问某个点或某条边被经过的次数,树上差分就可以派上用场了。这就是树上差分的基本操作。

  • 树上差分,就是利用差分的性质,对路径上的重要节点进行修改而不是暴力全改)作为其差分数组的值,最后在求值时,利用dfs遍历求出差分数组的前缀和,就可以达到降低复杂度的目的。
    树上差分时需要求LCA
  • 对点和边的树上差分原理相同,实现略有不同,这里分开来讲

对点树上差分

>

对边树上差分

将边权移动到点上
边的两端点的权重+边的权重cnt[u] += x, cnt[v] += x
最近公共祖先的点权- 2x,cnt[lca] -= 2x

代码复现
import sys
import math
from collections import defaultdict

maxn = 110000
n, k = map(int, sys.stdin.readline().split())
maxx = int(math.log2(n))
edge = defaultdict(list)
head = [0] * (n + 1)
dep = [0] * (n + 1)
dlt = [0] * (n + 1)
fa = [[0] * 20 for _ in range(n + 1)]
cnt = 0

def add_edge(a, b, dis):
    global cnt
    cnt += 1
    edge[cnt] = [head[a], b, dis]
    head[a] = cnt
    cnt += 1
    edge[cnt] = [head[b], a, dis]
    head[b] = cnt

for _ in range(n - 1):
    a, b, dis = map(int, sys.stdin.readline().split())
    add_edge(a, b, dis)

dfs(1)

for _ in range(k):
    a, b, x = map(int, sys.stdin.readline().split())
    dlt[a] += x
    dlt[b] += x
    c = lca(a, b)
    dlt[c] -= 2 * x

dfs1(1)

for i in range(1, n + 1):
    print(dlt[i], end=' ')
print()

感悟

理解树上差分的应用场景是题目要求对树上的一段路径进行操作,并询问某个点或某条边被经过的次数。

蓝桥杯云课学习笔记分享,欢迎大佬们批评指正!

一直在进步就好咯!

by 闻不多

  • 7
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值