ST在线查询:

求LCA(最近公共祖先)的算法有好多,按在线和离线分为在线算法和离线算法。
离线算法有基于搜索的Tarjan算法较优,而在线算法则是基于dp的ST算法较优。
首先说一下ST算法。
这个算法是基于RMQ(区间最大最小值编号)的
而求LCA就是把树通过深搜得到一个序列,然后转化为求区间的最小编号。
比如说给出这样一棵树。

这里写图片描述

我们通过深搜可以得到这样一个序列:
节点ver 1 3 1 2 5 7 5 6 5 2 4 2 1 (先右后左)
深度R 1 2 1 2 3 4 3 4 3 2 3 2 1
首位first 1 4 2 11 5 8 6
那么我们就可以这样写深搜函数:

 

int tot, head[N], ver[2*N], depth[2*N], first[N], dis[N];
//ver:节点编号 R:深度 first:点编号位置 dir:距离
void dfs(int u ,int dep)
{
    vis[u] = true; ver[++tot] = u; first[u] = tot; depth[tot] = dep;
    for(int k=head[u]; k!=-1; k=e[k].next)
        if( !vis[e[k].v] )
        {
            int v = e[k].v , w = e[k].w;
            dis[v] = dis[u] + w;
            dfs(v,dep+1);
            ver[++tot] = u; depth[tot] = dep;
        }
}

搜索得到序列之后假如我们想求4 和 7的 LCA
那么我们找4和7在序列中的位置通过first 数组查找发现在6—11
即7 5 6 5 2 4 在上面图上找发现正好是以2为根的子树。而我们只要找到其中一个深度最小的编号就可以了、
这时候我们就用到了RMQ算法。
维护一个dp数组保存其区间深度最小的下标,查找的时候返回就可以了。
比如上面我们找到深度最小的为2点,返回其编号10即可。
这部分不会的可以根据上面链接研究一些RMQ
代码可以这样写:

以上摘自http://blog.csdn.net/y990041769/article/details/40887469

以下是整体的代码:

#include <cstdio>
#include <cstring>
const int MAXNODE = 100010;
const int MAXEDGE = 200010;

struct Edge {
    int u, v, dis, next;
    Edge() {}
    Edge(int u, int v, int dis, int next): u(u), v(v), dis(dis), next(next) {}
};

struct ST {
    Edge edges[MAXEDGE];
    //n代表的是有多少个点,m代表的是有多少条边,tot表示的是dfs是遍历的点
    int n, m, tot;
    //head是链表的投,depth表示的是结点的深度,first表示的是结点第一次出现的位置,ver表示的是第几个位置存的是哪个结点,dis表示的是根结点到该结点的距离
    int head[MAXNODE], depth[MAXNODE], first[MAXNODE], ver[MAXNODE], dis[MAXNODE];
    //dp[i][j]表示的是以i开始的,长度为2^j的区间内的最小值
    int dp[MAXNODE][64];
    bool vis[MAXNODE];

    void init(int n) {
        this->n = n;
        m = tot = 0;
        memset(head, -1, sizeof(head));
        memset(vis, 0, sizeof(vis));
    }

    void AddEdge(int u, int v, int dis) {
        edges[m] = Edge(u, v, dis, head[u]);
        head[u] = m++;
    }

    //ver记录的是第tot个结点是哪个,first记录的是结点u第一次出现的位置,depth记录的是第tot个结点的深度
    void dfs(int u, int dep) {
        vis[u] = true; ver[++tot] = u; first[u] = tot; depth[tot] = dep;
        for (int i = head[u]; ~i; i = edges[i].next) {
            int v = edges[i].v;
            if (!vis[v]) {
                //遍历完子结点后,得在标记一下,因为两个子结点之间的最近公共祖先是u
                dis[v] = dis[u] + edges[i].dis;
                dfs(v, dep + 1);
                ver[++tot] = u; depth[tot] = dep;
            }
        }
    }

    void RMQ() {
        //dp记录的是下标从i开始,长度为2的j次方这段区间内深度最小的点是ver中的第几个
        for (int i = 1; i <= tot; i++)
            dp[i][0] = i;
        int a, b;
        for (int j = 1; (1 << j) <= tot; j++) 
            for (int i = 1; i + (1 << j) - 1 <= tot; i++) {
                a = dp[i][j - 1];
                b = dp[i + (1 << (j - 1))][j - 1];
                if (depth[a] < depth[b]) dp[i][j] = a;
                else dp[i][j] = b;
            }
    }

    void solve(int root) {
        dis[root] = 0;
        dfs(root, 1);
        RMQ();
    }

    //查询区间[x,y]中深度最小的那个数的位置
    int Query(int x, int y) {
        int k = 0;
        while (1 << (k + 1) <= y - x + 1) k++;
        int a = dp[x][k];
        int b = dp[y - (1 << k) + 1][k];
        //深度最小的那个才是最近公共祖先
        if (depth[a] < depth[b]) return a;
        return b;
    }

    //LCA查询的根据是两个点第一次出现的坐标,坐标之内的区间内,深度最小的那个点,就是他们的最近公共祖先
    int LCA(int x, int y) {

        x = first[x];
        y = first[y];
    //如果没有交换一下,就会出错
    //
        if (x > y) {
            x = x ^ y; y = x ^ y; x = x ^ y;
        }

    //找到该点的位置后,返回的是该点的值
        int c = Query(x, y);
        return ver[c];
    }

}st;

int main() {
    return 0;
}

 

Trajan离线做法:

 

posted on 2016-04-08 18:59  cleverbiger  阅读(145)  评论(0)    收藏  举报