树学习笔记
最近公共祖先(LCA)
LCA的常规求法包括:倍增,Tarjan
LCA的求法
倍增
预处理 : \(O(n\log n)\)
查询 : \(O(\log n)\)
在线
需要在处理时处理倍增,查询时先将两个点倍增至同一高度,然后再倍增找到最近祖先。
代码:
void dfs(int p,int fa){
dep[p]=dep[fa]+1;
st[p][0]=fa;
for(int i=1;i<=20;i++)st[p][i]=st[st[p][i-1]][i-1];
for(int i:e[p]){
if(i==fa)continue;
dfs(i,p);
}
}
int LCA(int l,int r){
if(dep[l]<dep[r])swap(l,r);
for(int i=20;i>=0;i--)if(dep[st[l][i]]>=dep[r])l=st[l][i];
if(l==r)return l;
for(int i=20;i>=0;i--){
if(st[l][i]!=st[r][i]){
l=st[l][i];
r=st[r][i];
}
}
return st[l][0];
}
Tarjan
预处理 \(O(an)\),查询 \(O(1)\),离线
在dfs的同时通过并查集来解决。
将两个点分别存入对方的一个询问队列,在遍历树的同时合并查找即可。
代码:
void dfs(int p,int f){
vis[p]=1;
for(auto i:g[p])if(vis[i.first])a[i.second].lca=find(i.first);
for(int i:e[p]){
if(i==f)continue;
dfs(i,p);
}
merge(p,f);
}
处理最短路径
边权最短路径
边权最短路径,先从上到下dfs,处理出根到这个点的距离。
类似于一个前缀和,两个点之间的路径长度为 \(pre_l+pre_r-2\times pre_{LCA(l,r)}\)
点权最小路径
与边权类似,不同之处在于2上述式子会把 \(LCA(l,r)\) 上的值删除两次。
所以式子可以修改为 \(pre_l+pre_r-2*pre_{LCA(l,r)}+a_{LCA(l,r)}\)
或者 \(pre_l+pre_r-pre_{LCA(l,r)}-pre_{fa_LCA(l,r)}\)
dfs序
定义:DFS序是对树进行深度优先搜索时,每个节点在刚进入递归后及即将回溯前各记录一次该点的编号,得到的长度为2N的节点序列。每个节点的编号在序列中恰好出现两次,第一次是进入节点的时间,第二次是退出节点的时间。(来自百度)
接下来讲一些dfs序的相关应用。
带单点修改子树求和
参考DFS 序 1
我们通过dfs序处理时不需要记录返回时的值,直接记录当前记录到哪里了,此时记录的第一次和第二次就是这个点的子树。
那么单点修改,区间查询,树状数组解决了。
带子树修改子树求和
参考DFS 序 2
改为区间修改,与上述同理。
路径修改,单点子树查询
通过树状数组实现区间修改,记录单点修改值和区间修改值
维护到根的前缀值即可,为区间操作的查分
单点子树修改,路径查询
照例维护一个单点情况和一个区间情况,通过区间修改维护到根的值
极度繁琐的差分式子推导,令人心态爆炸

浙公网安备 33010602011771号