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 闻不多