「学习笔记」tarjan求最近公共祖先

Posted 朝气蓬勃 后生可畏

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了「学习笔记」tarjan求最近公共祖先相关的知识,希望对你有一定的参考价值。

Tarjan 算法是一种 离线算法,需要使用并查集记录某个结点的祖先结点。
并没有传说中的那么快。

过程

将询问都记录下来,将它们建成正向边和反向边。
在 dfs 的过程中,给走过的节点打上标记,同时维护并查集,这里利用了回溯的思想,如果 \\(u\\) 节点的这棵子树没搜完,那么 fa[u] = u;,搜完后,在更新并查集。
我们假设查询 \\(u\\)\\(v\\) 的最近公共祖先,搜到节点 \\(u\\),如果另一个节点 \\(v\\) 已经被搜到过了,那么 \\(v\\) 点的并查集祖先就是 \\(u\\)\\(v\\) 的最近公共祖先。

如果第一次查询 \\(v\\) 点时,发现 \\(v\\) 点已经被搜到了,说明 \\(u\\)\\(v\\) 点在同一棵子树中,并且这个子树是所有包括了 \\(u\\) 点和 \\(v\\) 点的子树中 siz 最小的,这棵子树的根也是在所有符合条件的子树的根中离 \\(u\\)\\(v\\) 最近的,即这棵子树的根就是 \\(u\\)\\(v\\) 的最近公共祖先,而 \\(v\\) 的并查集祖先就是这个根节点。

代码

结构体记录询问

int cnt = 1;

struct query 
	int v, lca, nxt;
 q[N << 1];

记录询问、初始化

for (int i = 1, x, y; i <= m; ++ i) 
	scanf("%d%d", &x, &y);
	add_query(x, y);
	add_query(y, x);

for (int i = 1; i <= n; ++ i) 
	fa[i] = i;

tarjan、并查集

void tarjan(int u) 
	fa[u] = u;
	vis[u] = 1;
	for (int v : son[u]) 
		if (!vis[v]) 
			tarjan(v);
			fa[v] = u;
		
	
	int v;
	for (int i = h[u]; i; i = q[i].nxt) 
		if (vis[v = q[i].v]) 
			q[i].lca = q[i ^ 1].lca = find(v);
		
	

fa[u] = u; 是为了保证在 \\(u\\) 这棵子树没有搜完的情况下,让它子树里的节点的并查集找祖先时找到的是它。

Tarjan 算法求 LCA / Tarjan 算法求强连通分量




洛谷P3379 【模板】最近公共祖先(LCA)

#include<iostream>
#include<string>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#include <set>
#include <stack>
#include <map>
#include<vector>
using namespace std;
const int maxn = 5e5 + 5;
struct query
{
    int x;
    int y;
    int lca;
}query[maxn];
int fa[maxn];
bool vis[maxn];
int deep[maxn];
vector<int> G[maxn];
vector<int> Q[maxn];

void init()
{
    for (int i = 0; i < maxn; i++)
    {
        fa[i] = i;
    }
}

int find(int x)
{
    if (fa[x] == x)
        return x;
    else
        return fa[x] = find(fa[x]);
}

void Union(int v, int u)
{
    int vfa = find(v), ufa = find(u);
    if (vfa == ufa)return;
    else fa[v] = u;
}

void tarjan(int u)
{
    vis[u] = true;
    for (auto qid : Q[u]) {
        if (query[qid].x == u) {
            if (vis[query[qid].y]) {
                query[qid].lca = find(query[qid].y);
            }
        }
        else
        {
            if (vis[query[qid].x]) {
                query[qid].lca = find(query[qid].x);
            }
        }
    }
    for (auto v : G[u]) {
        if (vis[v])
            continue;
        deep[v] = deep[u] + 1;
        tarjan(v);
        Union(v, u);
    }
}

int main()
{
    init();
    int n, m, s;
    scanf_s("%d%d%d", &n, &m, &s);
    int x, y;
    for (int i = 1; i < n; i++)
    {
        scanf_s("%d%d", &x, &y);
        G[x].push_back(y);
        G[y].push_back(x);
    }
    for (int i = 1; i <= m; i++)
    {
        scanf_s("%d%d", &query[i].x, &query[i].y);
        Q[query[i].x].push_back(i); // 对于节点 x 来说有一个编号为 i 的询问
        Q[query[i].y].push_back(i);
    }
    tarjan(s);
    for (int i = 1; i <= m; i++)
    {
        printf("%d
", query[i].lca);
    }
    for (int i = 1; i <= n; i++)
    {
        printf("节点 %d 的深度是 %d
", i, deep[i]);
    }
}

FOJ 1628 计算公共祖先的个数

#include<iostream>
#include<string>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#include <set>
#include <stack>
#include <map>
#include<vector>
using namespace std;
const int maxn = 5e5 + 5;
struct query
{
    int x;
    int y;
    int lca;
}query[maxn];
int fa[maxn];
bool vis[maxn];
int deep[maxn];
vector<int> G[maxn];
vector<int> Q[maxn];

void init()
{
    for (int i = 0; i < maxn; i++)
    {
        fa[i] = i;
    }
}

int find(int x)
{
    if (fa[x] == x)
        return x;
    else
        return fa[x] = find(fa[x]);
}

void Union(int v, int u)
{
    int vfa = find(v), ufa = find(u);
    if (vfa == ufa)return;
    else fa[v] = u;
}

void tarjan(int u)
{
    vis[u] = true;

    for (auto qid : Q[u]) {
        if (query[qid].x == u) {
            if (vis[query[qid].y]) {
                query[qid].lca = find(query[qid].y);
            }
        }
        else
        {
            if (vis[query[qid].x]) {
                query[qid].lca = find(query[qid].x);
            }
        }
    }
    for (auto v : G[u]) {
        if (vis[v])
            continue;
        deep[v] = deep[u] + 1;
        tarjan(v);
        Union(v, u);
    }
}

int main()
{
    init();
    int n, k, m;
    int x, y;
    scanf_s("%d", &n);
    for (int i = 1; i <= n; i++)
    {
        scanf_s("%d", &k);
        for (int j = 1; j <= k; j++)
        {
            scanf_s("%d", &y);
            G[i].push_back(y);
            G[y].push_back(i);
        }
    }
    scanf_s("%d", &m);
    for (int i = 1; i <= m; i++) // 离线处理
    {
        scanf_s("%d%d", &query[i].x, &query[i].y);
        Q[query[i].x].push_back(i); // 对于节点 x 来说有一个编号为 i 的询问
        Q[query[i].y].push_back(i);
    }
    tarjan(1);
    /*for (int i = 1; i <= m; i++)
    {
        printf("%d
", query[i].lca);
    }
    for (int i = 1; i <= n; i++)
    {
        printf("节点 %d 的深度是 %d
", i, deep[i]);
    }*/
    for (int i = 1; i <= m; i++)
    {
        printf("%d
", deep[query[i].lca] + 1);
    }
}

以上是关于「学习笔记」tarjan求最近公共祖先的主要内容,如果未能解决你的问题,请参考以下文章

笔记:LCA最近公共祖先 Tarjan(离线)算法

tarjan算法求最近公共祖先

LCA(tarjan)

Tarjan 算法求 LCA / Tarjan 算法求强连通分量

最近公共祖先TarjanTarjan求LCA练习

LCA最近公共祖先 Tarjan离线算法