ST算法

来源:互联网 发布:数据统计公司创业 编辑:程序博客网 时间:2024/06/10 07:54

求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
代码可以这样写:

void ST(int n){    for(int i=1;i<=n;i++)        dp[i][0] = i;    for(int j=1;(1<<j)<=n;j++)    {        for(int i=1;i+(1<<j)-1<=n;i++)        {            int a = dp[i][j-1] , b = dp[i+(1<<(j-1))][j-1];            dp[i][j] = R[a]<R[b]?a:b;        }    }}//中间部分是交叉的。int RMQ(int l,int r){    int k=0;    while((1<<(k+1))<=r-l+1)        k++;    int a = dp[l][k], b = dp[r-(1<<k)+1][k]; //保存的是编号    return R[a]<R[b]?a:b;}int LCA(int u ,int v){    int x = first[u] , y = first[v];    if(x > y) swap(x,y);    int res = RMQ(x,y);    return ver[res];}

以上摘自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;}
0 0
原创粉丝点击