最近公共祖先

7 篇文章 0 订阅

最近公共祖先Least Common Ancestors(lca)

 

之前某区域赛银牌题是这个相关,发现高中信息学省赛也有这个...又发现不是太难 可以写一发

 

最近公共祖先就是在一颗树中某2节点最近的公共祖先 字面意思

树的图论定义是任意两点有且仅有一条简单路 所以知道树中2点的lca就可以O(1)的算出最短路或其他相关数据 显然比直接用最短路算法快(hdu 2586)

 

但树和无向无环图是有区别的 无向无环图是双向边 可以以任一点为根 而树是有向边(x是y的父节点 不能颠倒)需要找出唯一存在的根(所以在poj1330直接套hdu2586的代码样例都过不了)

 

直接说算法好了..最高效的是tarjan算法(图论其他相关问题也会用到) 对于n个点m组询问 时空复杂度都是O(n+m) (并查集复杂度视为常数的话)这是复杂度下界 不可能被卡时间(所以对于无向无环图 n个点求q组最短路只要O(n+q)线性的时间)

算法特点是用链表一次存下所有边和所有询问 对根执行tarjan算法 执行同时得到每点对于这个根的深度(就是每个点到根的最短路)

写起来有点复杂 需要并查集 链表存边 和一些中间数组 当做模板好了

 

还有在线的倍增算法,复杂度为O((n+q)logn),但是是有必要掌握的,有的题目不可以离线算。这里就不介绍了..

 

以Hdu 2586为例 一种O(n+m)内存常数比较小的写法 可以当做模板:

#include <iostream>

#include <cstdio>

#include <cstring>

using namespace std;

#define N 40005

#define M 202

struct { int to, w, ne; }e[N << 1];

struct { int to, ne; }qe[M << 1];

int qh[N], eh[N], ans[M], dis[N], pre[N], vis[N], et, qt;

//2个链表头 答案 到根的距离 并查集 vis数组

void init()

{

    et = qt = 0, memset(vis, -1, sizeof(vis));

    memset(eh, -1, sizeof(eh)), memset(qh, -1, sizeof(qh));

    memset(ans, -1, sizeof(ans));

}

void add1(int u, int v, int w)

{

    e[et].to = v, e[et].w = w, e[et].ne = eh[u], eh[u] = et++;

}

void add2(int u, int v)

{

    qe[qt].to = v, qe[qt].ne = qh[u], qh[u] = qt++;

}

int find(int u)

{

    if (pre[u] != u)

        pre[u] = find(pre[u]);

    return pre[u];

}

void lca(int u, int deep, int root)

{

    int i, v;

    pre[u] = u, dis[u] = deep, vis[u] = root;

    for (i = eh[u]; ~i; i = e[i].ne)

    {

        v = e[i].to;

        if (vis[v] == -1)

            lca(v, deep + e[i].w, root), pre[v] = u;

    }

    for (i = qh[u]; ~i; i = qe[i].ne)

    {

        v = qe[i].to;

        if (vis[v] == root)

            ans[i / 2] = dis[v] + dis[u] - 2 * dis[find(v)];

        //find(v)=lca(u,v)

    }

}

int main()

{

    int u, v, w, i, m, c;

    scanf("%*d");

    while (~scanf("%d%d", &m, &c))

    {

        init();

        for (i = 1; i < m; i++)

            scanf("%d%d%d", &u, &v, &w), add1(u, v, w), add1(v, u, w);

        for (i = 0; i < c; i++)

            scanf("%d%d", &u, &v), add2(u, v), add2(v, u);

        lca(1, 0, 1);

        for (i = 0; i < c; i++)

            if (ans[i] == -1) puts("Not connected");

            else printf("%d\n", ans[i]);

    }

}

主要是分清树和图的区别 和看出表面无关的题目怎么和lca扯上的关系..

 

Poj 1330 树的lca 找到根执行tarjan算法就好 只有一组询问 复杂度O(n)

Hdu 2586图的lca 随便找一点执行lca u到v的最短路是uv到lca(u,v)距离的和减去2*根到lca(u,v)的距离

计蒜客 小X的佛光 noip题目 图的lca 推出O(1)的公式算出所有lca 处理输出就好

Poj 1986 据说是无环图所以直接lca就能过 和hdu2586一样 但感觉题目没明确说无环..

Hdu 2874 森林里找lca 稍微处理下就好 模板不好会卡内存...

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值