• 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
  • 相关阅读:
    全球各国各类在轨与退役卫星数量与详细参数信息数据下载网站整理[转]
    土地覆盖土壤类型水体流域数据下载网站整理【转】
    Prometheus Operator配置钉钉告警
    Prometheus Operator自动发现功能修改 prometheus.yml文件
    prometheus operator 监控mysqlexporter
    prometheus operator 监控redisexporter
    Prometheus Operator 安装配置|最新版
    Linux 命令之 lsof 列出当前系统已打开的文件列表
    linux内核编译过程的最终总结版
    Linux内核裁剪的具体步骤
  • 原文地址:https://www.cnblogs.com/gray035/p/3085003.html
Copyright © 2020-2023  润新知