zoukankan      html  css  js  c++  java
  • 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
  • 相关阅读:
    UILabel 设置字体间的距离 和 行与行间的距离
    IB_DESIGNABLE 和 IBInspectable 的使用
    干货博客
    GitHub克隆速度太慢解决方案
    实时(RTC)时钟,系统时钟和CPU时钟
    折腾了好久的vscode配置c/c++语言环境(Windows环境下)
    c语言中的malloc函数
    记录一下关于在工具类中更新UI使用RunOnUiThread犯的极其愚蠢的错误
    记录关于Android多线程的一个坑
    Android中限制输入框最大输入长度
  • 原文地址:https://www.cnblogs.com/gray035/p/3085003.html
Copyright © 2011-2022 走看看