Tarjan发明的又一种算法,与连通性没关系,求 LCA 用的。
一种离线算法,需要等到所有查询都给出后统一进行处理和求解,但是时间效率非常高,可以达到
思路
主要思路如下:
我们维护一个并查集,存储在目前遍历完(回溯完毕)的点中每个点的深度最前的祖先,并从根节点开始 dfs 。设查询的一对点为 和
,
比
更早被遍历,当遍历完
回溯到某个祖先
时发现两点同属于以
为根的子树中,节点
即为
,
的最近公共祖先 LCA 。
形象地理解,最初每个节点独属于一个集合,每次回溯时把这个集合合并到父节点所在集合里,已回溯的最浅祖先节点为“队长”。找到点 时发现点
已经合并到能 dfs 到
的集合里了,此时
的队长即为最近公共祖先(此时它还不是
的队长,
还未回溯完毕)
代码
代码框架如下:
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
}
模板
题目大意就是给定一棵 有 个节点的树,树上每个点有点权。有
次查询,每次给定两个点
和
,求
到
路径中所有节点点权的异或和
#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 算法时间复杂度为 ,其中
是节点数,
是查询数,
是反阿克曼函数(极小,可视为常数)。
- DFS 遍历:耗时
,每个节点访问一次。
- 并查集操作:每次
find或合并操作平均,整体
。
- 查询处理:每个查询在
时间内处理(通过并查集查找)。
因此,总时间复杂度接近线性,非常高效。
929

被折叠的 条评论
为什么被折叠?



