Lca的两种做法

昨晚西山居第一场的比赛中的最后一题,很明显的Lca,不过发现自己居然没有模板- -,以前都没做过。。。最后网上搞了个模板各种修改,wa了n把终于过了。。。

今天来总结下以便下次碰到不至于这么坑。。。

在线算法,Lca+Rmq:

/****************
 *西山居第一场LCA*
     (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.
 ****************/
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<map>
#include<cmath>
using namespace std;
#define N 100009
struct edge{
    int v;
    int next;
}e[N*2];//由上可知为双向边
int ecnt;
int head[N];
int vis[N];
int n;
int R[N];//在rmq中的位置
int p[N*2];//原标号
int dis[N];//点到根的距离
int dep[N*2];//深度点标号重新标过,p[num]指向原来的标号
int dp[19][N*2];//Rmq
int num;
map<string,int> mpp;
void init(){
    memset(head,-1,sizeof(head));
    memset(R,-1,sizeof(R));
    memset(vis,0,sizeof(vis));
    memset(dis,0,sizeof(dis));
    mpp.clear();
    ecnt=0;
}
void add(int u,int v){  
    e[ecnt].v = v;  
    e[ecnt].next = head[u];  
    head[u] = ecnt++;  
    e[ecnt].v = u;  
    e[ecnt].next = head[v];  
    head[v] = ecnt++;  
}
void dfs(int u,int depth){  
    vis[u] = 1;  
    p[++num] = u;  
    dep[num] = depth;
    dis[u] = depth;
    for(int i=head[u];i!=-1;i=e[i].next){  
        int v = e[i].v;
        if(!vis[v]){  
            dfs(v,depth+1);  
            p[++num] = u;  
            dep[num] = depth;  
        }  
    }  
}  
void init_rmq(){
    int i,j;
    for(i=1;i<=num;i++){
        if(R[p[i]] == -1){
            R[p[i]] = i;
        }
    }
    for(i=1;i<=num;i++){
        dp[0][i] = i;
    }
    int t = (int)(log(num*1.0)/log(2.0));
    for(i=1;i<=t;i++){
        for(j=1;j+(1<<(i-1))<=num;j++){
            int a = dp[i-1][j],b = dp[i-1][j+(1<<(i-1))];
            if(dep[a]<=dep[b]){
                dp[i][j] = a;
            } else dp[i][j] = b;
        }
    }
}
int rmq(int u,int v){
    int s = R[u],t = R[v];
    if(s>t)swap(s,t);
    int k = (int)(log((t-s+1)*1.0)/log(2.0));
    int a = dp[k][s],b = dp[k][t-(1<<k)+1];
    if(dep[a]<=dep[b])return p[a];
    else return p[b];
}
int du[N];
char sta[55],End[55];
int main(){
    int t,m;
    scanf("%d",&t);
    while(t--){
        scanf("%d%d",&n,&m);
        int i,j;
        int nn = 1;
        init();
        for(i=0;i<=n;i++)du[i] = 0;
        for(i=1;i<n;i++){
              scanf("%s%s",sta,End);
            string A = sta;
            string B = End;
            if(mpp[A] == NULL)
                mpp[A] = nn++;
            if(mpp[B] == NULL)
                mpp[B] = nn++;
            du[mpp[A]] = 1;
            add(mpp[B],mpp[A]);
        }
        int root=1;
        for(i=1;i<=n;i++){
            if(!du[i]){
                root = i;
                break;
            }
        }
        num = 0;
        dep[root] = 0;
        dfs(root,0);
        init_rmq();
        for(i = 0; i < m; i++) {
            scanf("%s%s",sta,End);
            string A = sta;
            string B = End;
            if(A == B) {
                printf("0\n");
                continue;
            }
            int root1 = rmq(mpp[A],mpp[B]);
            if(mpp[B] == root1)
                printf("%d\n",dis[mpp[A]]-dis[root1]);
            else
                printf("%d\n",dis[mpp[A]]-dis[root1]+1);
        }
    }
    return 0;
}
View Code

离线算法,要把所有问题保存起来:

/**********************************************
 *hdu 2586
 *在求解最近公共祖先为问题上,用到的是Tarjan的思想
 *从根结点开始形成一棵深搜树,非常好的处理技巧就是
 *在回溯到结点u的时候,u的子树已经遍历,这时候才把
 *u结点放入合并集合中,这样u结点和所有u的子树中的结
 *点的最近公共祖先就是u了,u和还未遍历的所有u的兄弟
 *结点及子树中的最近公共祖先就是u的父亲结点。以此类推
 *这样我们在对树深度遍历的时候就很自然的将树中的结点
 *分成若干的集合,两个集合中的所属不同集合的任意一
 *对顶点的公共祖先都是相同的,也就是说这两个集合的最
 *近公共最先只有一个。对于每个集合而言可以用并查集来
 *优化,时间复杂度就大大降低了,为O(n + q),n为总
 *结点数,q为询问结点对数。
 ***********************************************/
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<map>
#include<cmath>
using namespace std;
#define N 40009
#define M 209
struct edge{
    int v;
    int c;
    int next;
}e[N*2],E[M*2];

int k1,k2;
int head1[N],head[N];
int vis[N];
int dis[N];
int fa[N];
int in[N];
int res[M][3];//保存答案

void init(){
    memset(head1,-1,sizeof(head1));
    memset(head,-1,sizeof(head));
    memset(vis,0,sizeof(vis));
    memset(dis,0,sizeof(dis));
    memset(in,0,sizeof(in));
    k1 = k2 = 0;
}

void add1(int u,int v,int c){  
    e[k1].v = v;  
    e[k1].next = head1[u]; 
    e[k1].c = c;
    head1[u] = k1++;  
    e[k1].v = u;  
    e[k1].next = head1[v];
    e[k1].c = c;
    head1[v] = k1++;  
}

void add2(int u,int v,int c){  
    E[k2].v = v;  
    E[k2].c = c;
    E[k2].next = head[u];  
    head[u] = k2++;  
    E[k2].v = u;
    E[k2].c = c;
    E[k2].next = head[v];  
    head[v] = k2++;  
}

int find(int x){
    if(x != fa[x]){
        return fa[x] = find(fa[x]);
    }
    return x;
}

void tarjan(int u) {
    vis[u] = 1;
    fa[u] = u;
    for(int i = head[u]; i != -1; i = E[i].next) {
        int v = E[i].v;
        if(vis[v]) {
            res[E[i].c][2] = find(v);
        }
    }

    for(int i = head1[u]; i != -1; i = e[i].next) {
        int v = e[i].v;
        if(!vis[v]) {
            dis[v] = dis[u] + e[i].c;
            tarjan(v);
            fa[v] = u;//递归出来的时候把子节点指向根节点
        }
    }
}

int main() {
    int T;
    scanf("%d",&T);
    while(T--) {
        init();
        int n,m;
        scanf("%d%d",&n,&m);
        for(int i = 0; i < n-1; i++) {
            int u,v,c;
            scanf("%d%d%d",&u,&v,&c);
            in[v]++;
            add1(u,v,c);
        }
        for(int i = 0; i < m; i++) {
            int u,v;
            scanf("%d%d",&u,&v);
            res[i][0] = u;
            res[i][1] = v;
            add2(u,v,i);
        }
        for(int i = 1; i <= n; i++) {
            if(in[i] == 0) {
                tarjan(i);
                break;
            }
        }
        for(int i = 0; i < m; i++) {
            printf("%d\n",dis[res[i][0]] + dis[res[i][1]] - 2*dis[res[i][2]]);
        }
    }
    return 0;
}
View Code
原文地址:https://www.cnblogs.com/gray035/p/3085003.html