lca的三种算法【倍增 / RMQ / Tajan】

来源:互联网 发布:电影语言翻译软件 编辑:程序博客网 时间:2024/06/05 05:23
  • lca:树上两点的最近公共祖先

倍增算法(在线)

  1. 利用dfs记录结点深度deep[i],并求出单点祖先:f[i][j]:从结点i向上出发2j 步的祖先,可推出→
    f[i][0] = fa[i], f[i][j] = f[f[i][j - 1]][j - 1]
  2. 求两点x,y的lca,不妨令deep[x] >= deep[y],x先爬deep[x]-deep[y]步使得x和y的deep相同;若此时x == y,则x就是最近公共祖先;否则x和y同时爬一些步数后,若到达的点相同,则找到公共祖先,第一个这样的点,就是最近公共祖先。
  3. 代码:
inline void dfs(int u, int fa){    deep[u] = deep[fa] + 1;    f[u][0] = fa;    for(int i = 1; i <= 20 && f[u][i - 1] > 0; ++ i)        f[u][i] = f[f[u][i - 1]][i - 1];    for(int i = head[u]; i; i = e[i].next)    if (e[i].to != fa) dfs(e[i].to, u);}inline int lca(int x, int y){    if (deep[x] < deep[y]) swap(x, y);    for(int i = 20; i >= 0; -- i)    if (deep[f[x][i]] >= deep[y]) x = f[x][i];    if (x == y) return x;    for(int i = 20; i >= 0; -- i)    if (f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];    return f[x][0];}
  • 时间复杂度O(n * log n + q * log n)

RMQ算法(在线)

  1. dfs并用数组a[0]按时间戳递增记录访问与回溯的结点,用a[1]记录a[0]中结点的深度,并用fir数组标记每一个点在a[0]中第一次出现的时间。
    如:【手动画图莫嫌弃QAQ
    对于上图中的树,
    a[0] = {1, 2, 4, 2, 5, 6, 5, 7, 5, 2, 1, 3, 8, 3, 1};
    a[1] = {0, 1, 2, 1, 2, 3, 2, 3, 2, 1, 0, 1, 2, 1, 0};
    fir = {1, 2, 12, 3, 5, 6, 8, 13};
  2. 经过推敲发现,对于两个点x,y的lca为a[1]中(fir[x], fir[y])的最小值所对应a[0]的点,可用RMQ快速查询。
  3. 代码略去。因与倍增同为在线,我较少使用【但貌似效率较高。推荐一版代码。

Tajan算法(离线)

  1. Tarjan算法基于从根节点开始DFS时,在刚刚好访问完两个节点时,这两个节点的LCA必定没有在DFS中回溯。
  2. 当访问完x的每棵子树时,用并查集把这些子树记为与x同集。
  3. 可发现当dfs访问到v时,u以访问,lca(u, v) = find(v)。【建议手模一下】
  4. 代码:
inline int find(int x){    return p[x] == x ? x : p[x] = find(p[x]);}inline void dfs(int u, int fa){    vi[u] = true;    for(int i = head[u]; i; i = e[i].next)    if (e[i].to != fa)    {        int v = e[i].to;        dfs(v, u);        p[v] = u;    }    for(int i = qh[u]; i; i = q[i].next)    if (vi[q[i].to]) ans[q[i].id] = find(q[i].to);}
  • 时间复杂度:O(n + q)

答案便于记录时,个人感觉tajan好用,简短而快速。

原创粉丝点击