树学习笔记

最近公共祖先(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

改为区间修改,与上述同理。

路径修改,单点子树查询

参考DFS 序 3,树上差分 1

通过树状数组实现区间修改,记录单点修改值和区间修改值

维护到根的前缀值即可,为区间操作的查分

单点子树修改,路径查询

参考K - DFS 序 4

照例维护一个单点情况和一个区间情况,通过区间修改维护到根的值

极度繁琐的差分式子推导,令人心态爆炸

posted @ 2025-11-28 10:01  huhangqi  阅读(5)  评论(0)    收藏  举报
/*
*/