当前位置: 代码迷 >> 综合 >> 求LCA两种方法整理(倍增法、tarjan)
  详细解决方案

求LCA两种方法整理(倍增法、tarjan)

热度:86   发布时间:2023-12-21 00:03:46.0
倍增法

复杂度为O(nlogn + qlogn)

#include<bits/stdc++.h>
using namespace std;const int maxn = 10005;
int parents[maxn][20], depth[maxn];
int n, from[maxn], root = -1;
vector<int> G[maxn];//------------------------------- 
void getDepth_dfs(int u) // DFS求深度 
{
    int len = G[u].size();for (int i = 0; i < len; ++i) {
    int v = G[u][i];depth[v] = depth[u] + 1;getDepth_dfs(v);}
}void getDepth_bfs(int u) // BFS求深度 
{
    queue<int> Q;Q.push(u);while (!Q.empty()) {
    int v = Q.front();Q.pop();for (int i = 0; i < G[v].size(); ++i) {
    depth[G[v][i]] = depth[v] + 1;Q.push(G[v][i]);}}
}void getParents()
{
    for (int up = 1; (1 << up) <= n; ++up) {
    for (int i = 1; i <= n; ++i) {
    parents[i][up] = parents[parents[i][up - 1]][up - 1];}}
}int Lca(int u, int v)
{
    if (depth[u] < depth[v]) swap(u, v); // 使满足u深度更大, 便于后面操作 int i = -1, j;// i求的是最大二分跨度 while ((1 << (i + 1)) <= depth[u]) ++i;// 下面这个循环是为了让u和v到同一深度 for (j = i; j >= 0; --j) {
    if (depth[u] - (1 << j) >= depth[v]) {
     // 是>=, 因为如果<,代表跳过头了,跳到了上面. u = parents[u][j];}}if (u == v) return u; // 刚好是祖宗 // u和v一起二分找祖宗for (j = i; j >= 0; --j) {
    if (parents[u][j] != parents[v][j]) {
    u = parents[u][j];v = parents[v][j];}}return parents[u][0]; // 说明上个循环迭代到了Lca的子结点 
}
//------------------------------- void getData()
{
    cin >> n;int u, v;for (int i = 1; i < n; ++i) {
    cin >> u >> v;G[u].push_back(v);parents[v][0] = u;from[v] = 1;}for (int i = 1; i <= n; ++i) {
    if (from[i] == -1) root = i;}
}void questions()
{
    int q, u, v;cin >> q;for (int i = 0; i < q; ++i) {
    cin >> u >> v;int ans = Lca(u, v);cout << ans << endl;//cout << u << " 和 " << v << " 的最近公共祖先(LCA)是: " << ans << endl; }
}void init()
{
    memset(parents, -1, sizeof(parents));memset(from, -1, sizeof(from));memset(depth, -1, sizeof(depth));
}int main()
{
    init();getData();depth[root] = 1;getDepth_dfs(root);//getDepth_bfs(root);getParents();questions();return 0;
}
/* 9 1 2 1 3 1 4 2 5 2 6 3 7 6 8 7 9 5 1 3 5 6 8 9 8 4 5 8 */

tarjan

为离线算法
复杂度为O(n + q)

#include<bits/stdc++.h>
using namespace std;const int MAXN = 10005;
vector<int> vec[MAXN];
bool vis[MAXN];int per[MAXN],head[MAXN],in_num[MAXN];
//in_num统计每个点的入度,为了求根节点,per和并查集中的作用相同,head配合结构体前向星
int cnt,n,m;struct Node
{
    int c,next;
}edge[MAXN];void Init()
{
    cnt = 0;memset(in_num,0,sizeof(in_num));memset(head,-1,sizeof(head));memset(vis,0,sizeof(vis));for(int i =1;i <= n;i++){
    vec[i].clear();per[i] = i;}
}void add(int x,int y)
{
    edge[++cnt].next = head[x];edge[cnt].c = y;head[x] = cnt;
}int Find(int x)
{
    if(per[x] != x)per[x] = Find(per[x]);return per[x];
}void Union(int x,int y)
{
    x = Find(x);y = Find(y);if(x == y)return ;per[x] = y;
}void Tarjan(int x)
{
    for(int i = head[x];i != -1; i =edge[i].next){
    int v = edge[i].c;Tarjan(v);Union(v,x);//首先要一直遍历的叶子节点}vis[x] = 1; // 当这个节点的所有子节点都已经遍历到了,就标记这个节点for(int i = 0;i < vec[x].size();i ++)if(vis[vec[x][i]])//然后在问题中寻找是否有关于这两个节点都已经标记过的了printf("%d 和 %d 的LAC是 %d\n",x,vec[x][i],Find(vec[x][i]));
}
int main()
{
    int x,y;scanf("%d%d",&n,&m);Init();for(int i = 1;i < n;i++){
    scanf("%d%d",&x,&y);add(x,y);in_num[y] ++;}for(int i = 0;i < m;i ++){
    scanf("%d%d",&x,&y);vec[x].push_back(y);vec[y].push_back(x);}int root;for(int i = 1;i <= n;i ++)if(in_num[i] == 0)root = i;Tarjan(root);
}
/** 8 4 1 2 1 3 2 4 2 5 4 7 5 8 3 6 7 8 5 6 5 2 4 6 **/