AcWing 算法提高课 最近公共祖先(LCA)
1、向上标记法
从节点向上查找,O(n)
2、倍增法,预处理O(nlogn),查询O(qlogn)
记录当前节点向上2^j 步的祖先是谁
记录当前节点的深度depth[i]
哨兵:节点0,且depth[0]=0。当fa[i][j]已经跳出这棵树时,fa[i][j]=0
步骤:
(1) 先将两个节点跳到同一层
(2) 两个节点同时向上跳,直到跳到最近公共祖先的下一层
模板:

const int N=100010; int n; int root=-1; vector<int> adj[N]; int depth[N]; int pa[N][20];//第二维和n的范围对应 //初始化 void BFS() { memset(depth,0x3f,sizeof(depth)); depth[0]=0; depth[root]=1; queue<int> que; que.push(root); while(que.size()) { int u=que.front(); que.pop(); for(auto nxt:adj[u]) { if(depth[nxt]>depth[u]+1) { depth[nxt]=depth[u]+1; que.push(nxt); pa[nxt][0]=u; fore(i,1,16) { //预处理pa,倍增思想 pa[nxt][i]=pa[pa[nxt][i-1]][i-1]; } } } } } int LCA(int a,int b) { //交换,使a的深度>=b的深度 if(depth[a]<depth[b]) swap(a,b); for(int i=16;i>=0;i--) { if(depth[pa[a][i]]>=depth[b]) { a=pa[a][i]; } } if(a==b) return a; for(int i=16;i>=0;i--) { if(pa[a][i]!=pa[b][i]) { a=pa[a][i]; b=pa[b][i]; } } return pa[a][0]; } void YD() { cin>>n; ////输入//// int a,b; fore(i,1,n) { cin>>a>>b; if(b==-1) root=a; else { adj[a].push_back(b); adj[b].push_back(a); } } ////输入//// BFS(); ////查询//// int q; cin>>q; while(q--) { int x,y; cin>>x>>y; int p=LCA(x,y); if(p==x) cout<<1<<endl; else if(p==y) cout<<2<<endl; else cout<<0<<endl; } ////查询//// }
3、Tarjan——离线求LCA,查询O(n+q)

int n,q; const int N=10010; vector<int> adj[N]; vector<int> cost[N]; vector<pair<int,int>> query[N];//v,qid int res[2*N]; int dis[N]; int mark[N]; int p[N]; void DFS(int u,int pa) { for(int i=0;i<adj[u].size();i++) { int nxt=adj[u][i]; int c=cost[u][i]; if(nxt!=pa) { dis[nxt]=dis[u]+c; DFS(nxt,u); } } } int find(int x) { if(p[x]!=x) p[x]=find(p[x]); return p[x]; } void Tarjan(int u) { mark[u]=1; for(int i=0;i<adj[u].size();i++) { int nxt=adj[u][i]; int c=cost[u][i]; if(mark[nxt]==0)//子节点而非父节点 { Tarjan(nxt); p[nxt]=u; } } for(auto [v,id]:query[u]) { if(mark[v]==2) { int pa=find(v); res[id]=dis[u]+dis[v]-2*dis[pa]; } } mark[u]=2; } void YD() { cin>>n>>q; int a,b,c; fore(i,1,n-1) { cin>>a>>b>>c; adj[a].push_back(b); cost[a].push_back(c); adj[b].push_back(a); cost[b].push_back(c); } fore(i,1,q) { cin>>a>>b; query[a].push_back({b,i}); query[b].push_back({a,i}); } fore(i,1,n) p[i]=i; DFS(1,0);//get dis Tarjan(1); fore(i,1,q) { cout<<res[i]<<endl; } }
利用并查集将绿色合并到其子树的根节点,然后显然可以求出与红色节点的LCA,其中,红色路线是当前的DFS栈。
注意先进行DFS子节点,当子节点DFS完成后,回到当前节点,再进行查询。
模板:

int n,q; const int N=10010; vector<int> adj[N]; vector<int> cost[N]; vector<pair<int,int>> query[N];//v,qid int res[2*N]; int dis[N]; int mark[N]; int p[N]; void DFS(int u,int pa) { for(int i=0;i<adj[u].size();i++) { int nxt=adj[u][i]; int c=cost[u][i]; if(nxt!=pa) { dis[nxt]=dis[u]+c; DFS(nxt,u); } } } int find(int x) { if(p[x]!=x) p[x]=find(p[x]); return p[x]; } void Tarjan(int u) { mark[u]=1; for(int i=0;i<adj[u].size();i++) { int nxt=adj[u][i]; int c=cost[u][i]; if(mark[nxt]==0)//子节点而非父节点 { Tarjan(nxt); p[nxt]=u; } } for(auto [v,id]:query[u]) { if(mark[v]==2) { int pa=find(v); res[id]=dis[u]+dis[v]-2*dis[pa]; } } mark[u]=2; } void YD() { cin>>n>>q; int a,b,c; fore(i,1,n-1) { cin>>a>>b>>c; adj[a].push_back(b); cost[a].push_back(c); adj[b].push_back(a); cost[b].push_back(c); } fore(i,1,q) { cin>>a>>b; query[a].push_back({b,i}); query[b].push_back({a,i}); } fore(i,1,n) p[i]=i; DFS(1,0);//get dis Tarjan(1); fore(i,1,q) { cout<<res[i]<<endl; } }
4、LCA可以解决多次查询树上两点间距离的问题
设dis[]为到根的距离,则x、y之间的距离为 dis[x]+dis[y]-2*dis[pa],pa为LCA(x,y)
例题:https://www.acwing.com/problem/content/1173/
代码:

#include<bits/stdc++.h> #define fore(x,y,z) for(LL x=(y);x<=(z);x++) #define forn(x,y,z) for(LL x=(y);x<(z);x++) #define rofe(x,y,z) for(LL x=(y);x>=(z);x--) #define rofn(x,y,z) for(LL x=(y);x>(z);x--) #define pub push_back #define all(x) (x).begin(),(x).end() #define fi first #define se second using namespace std; typedef long long LL; typedef pair<int,int> PII; typedef pair<LL,LL> PLL; int n,q; const int N=10010; vector<int> adj[N]; vector<int> cost[N]; vector<pair<int,int>> query[N];//v,qid int res[2*N]; int dis[N]; int mark[N]; int p[N]; void DFS(int u,int pa) { for(int i=0;i<adj[u].size();i++) { int nxt=adj[u][i]; int c=cost[u][i]; if(nxt!=pa) { dis[nxt]=dis[u]+c; DFS(nxt,u); } } } int find(int x) { if(p[x]!=x) p[x]=find(p[x]); return p[x]; } void Tarjan(int u) { mark[u]=1; for(int i=0;i<adj[u].size();i++) { int nxt=adj[u][i]; int c=cost[u][i]; if(mark[nxt]==0)//子节点而非父节点 { Tarjan(nxt); p[nxt]=u; } } for(auto [v,id]:query[u]) { if(mark[v]==2) { int pa=find(v); res[id]=dis[u]+dis[v]-2*dis[pa]; } } mark[u]=2; } void YD() { cin>>n>>q; int a,b,c; fore(i,1,n-1) { cin>>a>>b>>c; adj[a].push_back(b); cost[a].push_back(c); adj[b].push_back(a); cost[b].push_back(c); } fore(i,1,q) { cin>>a>>b; query[a].push_back({b,i}); query[b].push_back({a,i}); } fore(i,1,n) p[i]=i; DFS(1,0);//get dis Tarjan(1); fore(i,1,q) { cout<<res[i]<<endl; } } int main() { ios_base::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr); int T=1; //cin >> T; while (T--) { YD(); } return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人