Lca转Rmq

Lca:求树上任意两个节点的最近公共祖先

Rmq:区间最值查询


    (1)
    / \

(2)   (7)

/ \     \

(3) (4)   (8)

    /   \

(5)    (6)

一个nlogn 预处理,O(1)查询的算法.

Step 1:

        按先序遍历整棵树,记下两个信息:结点访问顺序和结点深度.

        如上图:

        结点访问顺序是: 1 2 3 2 4 5 4 6 4 2 1 7 8 7 1 //共2n-1个值

        结点对应深度是: 0 1 2 1 2 3 2 3 2 1 0 1 2 1 0

Step 2:

        如果查询结点3与结点6的公共祖先,则考虑在访问顺序中

        3第一次出现,到6第一次出现的子序列: 3 2 4 5 4 6.

        这显然是由结点3到结点6的一条路径.

        在这条路径中,深度最小的就是最近公共祖先(LCA). 即

        结点2是3和6的LCA.

Step 3:

        于是问题转化为, 给定一个数组D,及两个数字i,j,如何找出

        数组D中从i位置到j位置的最小值..

        如上例,就是D[]={0,1,2,1,2,3,2,3,2,1,0,1,2,1,0}.

        i=2;j=7;


hdu4547:http://acm.hdu.edu.cn/showproblem.php?pid=4547


#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <string.h>
#include <string>
#include <map>

using namespace std;

const int MAXN = 100000 + 1;
const int char_len = 40 + 1;
int n, m, tot, root, step;

struct Edge
{
    int v, next;
}edge[MAXN * 2];

int e, head[MAXN], indegree[MAXN], deep[MAXN * 2], pos[MAXN], d[MAXN * 2][20];
char stra[char_len], strb[char_len];
map <string, int> mt;

void init()
{
    e = tot = step = 0;
    mt.clear();
    memset(head, -1, sizeof(head));
    memset(pos, -1, sizeof(pos));
    memset(indegree, 0, sizeof(indegree));
}

void add(int u, int v)
{
    edge[e].v = v;
    edge[e].next = head[u];
    head[u] = e++;
}

void Lca_to_Rmq(int p, int dfn)
{
    if (pos[p] == -1) pos[p] = step;
    deep[step++] = dfn;

    for (int i = head[p]; i != -1; i = edge[i].next)
    {
        Lca_to_Rmq(edge[i].v, dfn + 1);
        deep[step++] = dfn;
    }
}

void Init_Rmq()
{
    for (int i = 0; i < 2 * n - 1; i++) d[i][0] = deep[i];

    for (int j = 1; (1 << j) <=  2 * n - 1; j++)
        for (int i = 0; i + (1 << j) - 1 < 2 * n - 1; i++)
            d[i][j] = min(d[i][j - 1], d[i + (1 << (j - 1))][j - 1]);
}

int Rmq(int l, int r)
{
    int k = 0;
    if (l > r)
    {
        int temp = l;
        l = r;
        r = temp;
    }

    while ((1 << (k + 1)) <= r - l + 1) k++;
    return min(d[l][k], d[r - (1 << k) + 1][k]);
}

void input()
{
    int t, u, v;

    scanf("%d", &t);

    while (t--)
    {
        init();
        scanf("%d %d", &n, &m);

        for (int i = 0; i < n - 1; i++)
        {
            scanf("%s %s", stra, strb);
            if (!mt[stra]) mt[stra] = ++tot;
            if (!mt[strb]) mt[strb] = ++tot;

            u = mt[stra], v = mt[strb];
            indegree[u]++;
            add(v, u);
        }

        for (int i = 1; i <= n; i++)
        {
            if (indegree[i] == 0)
            {
                root = i;
                break;
            }
        }

        Lca_to_Rmq(root, 0);

        Init_Rmq();

        for (int i = 0; i < m; i++)
        {
            scanf("%s %s", stra, strb);
            u = mt[stra], v = mt[strb];
            int f = Rmq(pos[u], pos[v]);
            if (n == 1)
            {
                printf("0\n");
                continue;
            }
            printf("%d\n", deep[pos[u]] - f + (deep[pos[v]] == f ? 0 : 1));
        }
    }
}

int main()
{
    input();
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值