题解 P3379 【【模板】最近公共祖先(LCA)】

· · 题解

据说没什么Tarjan的题解?

爱离线的Oier看这里!

今天我来给大家介绍一下如何解决LCA问题

显而易见,LCA->LeastCommentAncestors->最近公共祖先问题

那么,容易想到一种暴力做法,即对于任意两点,先让更深的那个节点向上跳,与另个节点处于同一深度;再慢慢一级级爬,直到交与一点为止;很容易想到,当两点均为叶子节点而且LCA为根节点时,效率为O(h)的,这又不是平衡树,深度和n 一个级别是会死的。

有人会说用倍增,可在下不喜欢在线算法,我喜欢稍微快那么一点点的离线Tarjan。比起O((n + m) * logn)的倍增,Tarjan的常数α取决于并查集,在启发式或路径压缩下,是比logn小的。

那么,Tarjan算法过程是什么?

从根节点开始,深搜。 遍历当前访问节点u的所有子节点v,并标记这些子节点v已被访问过。若是v还有子节点,返回2,否则下一步。接着,合并v到u上。(并查集闪亮登场!!!)寻找与当前点u有询问关系的点v。若是v已经被访问过了,则可以确认u和v的最近公共祖先为v被合并到的父亲节点rt。

借用一下一位dalao的图,和思考来做说明

大佬的看法

比如:8−1−14−13 ,此时已经完成了对子树 11 的子树 14 的 dfs 与合并( 14 子树的集合与 11 所代表的集合合并),如果存在询问 (13,14) ,则其 LCA 即 getf(14) ,即 11 ;如果还存在由节点 13与 已经完成搜索的子树中的 节点的询问,那么处理完。然后合并子树 13 的集合与其父亲 11 当前的集合,回溯到子树 11 ,并深搜完所有 11 的其他未被搜索过的儿子,并完成子树 11 中所有节点的合并,再往上回溯,对节点 11 进行类似的操作即可。

综上,只要不强制在线,Tarjan可优秀了!

(LCA会强制在线?滑稽。只要出题人没病,卡Tarjan干嘛!)

不废话,上代码!!!

//Program written by Liu Zhaozhou ~~~
#include <bits/stdc++.h>
#include <algorithm>
#include <queue>
#include <set>
#include <vector>
#include <deque>

#define lowbit(x) x & -x

#pragma GCC optimize(3)

using namespace std;

inline char gc(void)
{
    static char buf[100000], *p1 = buf, *p2 = buf;
    return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF : *p1++;
}

template <class T> inline void read(register T &x)
{
    static T flag = 1; x = 0;
    register char ch = getchar();
    for (; !isdigit(ch); ch = getchar()) if (ch == '-') flag = -1;
    for (; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + (ch ^ 48);
    x *= flag; return;
}

template <class T> inline void write(register T x)
{
    if (x < 0) putchar('-'), x = -x;
    if (x > 9) write(x / 10);
    putchar(x % 10 + '0');
}

template <class T> inline void writeln(register T x)
{
    write(x);
    puts("");
}

template <class T> inline void writeln(T x, char c)
{
    write(x); putchar(c);
}

template <class T> inline void writeln(char c, T x)
{
    putchar(c); write(x);
}

template <class T> inline void chkmax(T &x, const T y) {x > y ? x = x : x = y;}
template <class T> inline void chkmin(T &x, const T y) {x < y ? x = x : x = y;}

typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;

enum {
    maxpool = 500010
};

struct Edge {
    int to, nxt;
} line[maxpool << 1];

struct Info {
    int to, id;
}; vector <Info> vec[maxpool];

int head[maxpool], cnt = 0, rt;

inline void add(int u, int v) {
    line[++cnt] = (Edge) {v, head[u]};
    head[u] = cnt; return;
}

inline void ins(int x, int y, int i) {
    vec[x].push_back((Info) {y, i});
    vec[y].push_back((Info) {x, i});
}

int n, m, ans[maxpool], vis[maxpool], f[maxpool];

inline int getf(int x) {
    return f[x] == x ? f[x] : f[x] = getf(f[x]);
}

inline void tarjan(int dep) {
    vis[dep] = 1;
    for (int e = head[dep]; e; e = line[e].nxt) {
        register int u = line[e].to;
        if (vis[u]) continue;
        tarjan(u); f[u] = dep;
    }

    for (register int i = 0; i < vec[dep].size(); i++)
    {
        int u = vec[dep][i].to, id = vec[dep][i].id;
        if (vis[u] == 2) ans[id] = getf(u);
    }

    vis[dep] = 2; return;
}

int main(void)
{
    read(n); read(m); read(rt);
    for (register int i = 1; i < n; i++) {
        register int u, v;
        read(u); read(v);
        add(u, v); add(v, u);
    } 

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

    for (register int i = 1; i <= m; i++) {
        register int u, v;
        read(u); read(v);
        ins(u, v, i);
    }

    tarjan(rt);

    for (register int i = 1; i <= n; i++)
        writeln(ans[i]);

    return 0;
}

/*Tarjan Algorithm solves LCA problems*/