Tarjan 求解 LCA

Tarjan发明的又一种算法,与连通性没关系,求 LCA 用的。

一种离线算法,需要等到所有查询都给出后统一进行处理和求解,但是时间效率非常高,可以达到 O((n + q)\alpha(n))

思路

主要思路如下:

我们维护一个并查集,存储在目前遍历完(回溯完毕)的点中每个点的深度最前的祖先,并从根节点开始 dfs 。设查询的一对点为 uvuv 更早被遍历,当遍历完 u 回溯到某个祖先 p 时发现两点同属于以 p 为根的子树中,节点 p 即为 u , v 的最近公共祖先 LCA 。

形象地理解,最初每个节点独属于一个集合,每次回溯时把这个集合合并到父节点所在集合里,已回溯的最浅祖先节点为“队长”。找到点 v 时发现点 u 已经合并到能 dfs 到 v 的集合里了,此时 u 的队长即为最近公共祖先(此时它还不是 v 的队长,v 还未回溯完毕)

代码

代码框架如下:

void tarjan(int u,int fa){
    for(auto v : vec[u]){//dfs 邻接表存图
        if(v == fa) continue;
        vis[v] = true;//已访问
        tarjan(v,u);
        f[find(v)] = find(u);//合并并查集,将子节点合并至父节点
    }
    for(auto v : Q[u])//询问 Q数组存对点信息
        if(vis[v])//对点在当前子树内
            ans[make_pair(u,v)] = find(v);//LCA
}

模板 

一道可行的模板(下面是代码)

题目大意就是给定一棵 有 n 个节点的树,树上每个点有点权。有 m 次查询,每次给定两个点 u 和 v ,求 u 到 v 路径中所有节点点权的异或和

#include <bits/stdc++.h>
using namespace std;
#define rep(i,n) for(int i = 1;i <= n;i++)
#define repg(i,u) for(int i = head[u];i;i = e[i].nxt)
#define itn int
#define ll long long
const int N = 1e5 + 5;
struct edge{
	int v,nxt;
}e[N<<1];
int head[N],fa[N],w[N],dis[N],ans[N],tot,n,m,u,v;
inline void add(itn u,int v){
	e[++tot] = (edge){v,head[u]};
	head[u] = tot;
}
int find(int x){
	return fa[x] == x ? x : fa[x] = find(fa[x]);
}
bool vis[N];
struct Qry{
	int x,y;
}q[N];
struct node{
	int id,v;
};
vector <node> Q[N];
void dfs_tar(int u,int f){
	repg(i,u){
		int v = e[i].v;
		if(v == f) continue;
		dis[v] = dis[u] ^ w[v];//这道题求异或和
		dfs_tar(v,u);
		fa[find(v)] = find(u);
		vis[v] = true;
	}
	for(auto i : Q[u]) if(vis[i.v]) ans[i.id] = find(i.v);//ans 是存 LCA 用的
}
inline void read(int &x){
	x = 0;
	int f = 1;
	char c;
	for(c = getchar();!isdigit(c);c = getchar()) if(c == '-') f = -1;
	for(;isdigit(c);c = getchar()) x = (x << 3) + (x <<1) + (c ^ 48);
	x *= f;
}
inline void write(int x){
	if(x / 10) write(x / 10);
	putchar((x % 10 + '0'));
}
int main(){
	read(n),read(m);
	rep(i,n) read(w[i]),fa[i] = i;
	rep(i,n - 1){
		read(u),read(v);
		add(u,v),add(v,u);
	}
	rep(i,m){
		read(q[i].x),read(q[i].y);
		Q[q[i].x].emplace_back((node){i,q[i].y});
		Q[q[i].y].emplace_back((node){i,q[i].x});
	}
	dis[1] = w[1];
	dfs_tar(1,0);
	rep(i,m) write(dis[q[i].x] ^ dis[q[i].y] ^ w[ans[i]]),putchar('\n');
	return 0;
}

另外,洛谷的这道题也可以试试

复杂度

Tarjan 的 LCA 算法时间复杂度为 O((n + m) \alpha(n)),其中 n 是节点数,m 是查询数,\alpha(n) 是反阿克曼函数(极小,可视为常数)。

  • DFS 遍历:耗时 O(n),每个节点访问一次。
  • 并查集操作:每次 find 或合并操作平均 O(\alpha(n)),整体 O(n \alpha(n))
  • 查询处理:每个查询在 O(1) 时间内处理(通过并查集查找)。
    因此,总时间复杂度接近线性 O(n + m),非常高效。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值