zoukankan      html  css  js  c++  java
  • LCA(RMQ、倍增)

    一、RMQ求LCA

    先介绍一下三个数组的含义

    ver[i]表示dfs第i个访问的结点

    R[i]表示ver[i]所在的层数,也就是深度

    first[i]表示ver[i]第一次出现的下标

    举个栗子如下图

    求出来的三个数组如下图,f->first,v->ver,T就是点的编号

    如果我们要求d和f的LCA,我们发现d第一次出现在4,f第一次出现在9,由dfs的性质可以知道,在4~9之间深度最小的点就是LCA

    于是,我们可以用ST表来处理出各区间之间深度的RMQ,这样就可以对于每个询问只用常数的时间就能输出答案,并且预处理也只用nlogn,按照道理来说是比倍增要快的。可是。。。我发现不加读入优化就很悬。。时间卡的很紧,读入输出都要尽量优。。不要像我一开始用cout。。。好了,不扯淡了

    上代码

    #include<cmath>
    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    using namespace std;
    int n,m,s,tot=0,cnt=0;
    int head[1000100],nxt[1000100],to[1000100];
    int fir[1000100],ver[1000100],r[1000100];
    int f[20][1000100],rec[20][1000100];
    int read()
    {
        int x=0,flag=0;
        char ch=getchar();
        if(ch=='-') flag=1;
        while(ch<'0'||ch>'9')ch=getchar();
        while(ch>='0'&&ch<='9')x*=10,x+=ch-'0',ch=getchar();
        if(flag) return -x;
        return x;
    }
    void addedge(int x,int y)
    {
        cnt++;
        nxt[cnt]=head[x];
        head[x]=cnt;
        to[cnt]=y;
    }
    void dfs(int u,int dep)//dfs处理出三个数组
    {
        fir[u]=++tot,ver[tot]=u,r[tot]=dep;
        for(int i=head[u];i!=-1;i=nxt[i])
        {
            int v=to[i];
            if(!fir[v])
            {
                dfs(v,dep+1);
                ver[++tot]=u,r[tot]=dep;
            }
        }
    }
    int main()
    {
        memset(head,-1,sizeof(head));
        n=read(),m=read(),s=read();
        for(int i=1;i<n;i++)
        {
            int x,y;
            x=read(),y=read();
            addedge(x,y);
            addedge(y,x);
        }
        dfs(s,1);
        //ST表求RMQ,不会ST表的童鞋先去做一下ST表模板。。。
        for(int i=1;i<=tot;i++)
            f[0][i]=r[i],rec[0][i]=ver[i];
        for(int i=1;i<=log(tot)/log(2);i++)
            for(int j=1;j<=tot-(1<<i)+1;j++)
            {
                if(f[i-1][j]<f[i-1][j+(1<<(i-1))])
                    f[i][j]=f[i-1][j],rec[i][j]=rec[i-1][j];
                else f[i][j]=f[i-1][j+(1<<(i-1))],rec[i][j]=rec[i-1][j+(1<<(i-1))];
            }
        //rec记录的是区间内深度最小值的编号
        for(int i=1;i<=m;i++)
        {
            int l,r;
            l=read(),r=read();
            l=fir[l],r=fir[r];
            if(l>r) swap(l,r);
            int k=0;
            while((1<<k)<=r-l+1) k++;
            k--;
            if(f[k][l]<f[k][r-(1<<k)+1]) printf("%d
    ",rec[k][l]);
            else printf("%d
    ",rec[k][r-(1<<k)+1]);//常见的ST表输出
        }
        return 0;
    }

     

    二、倍增求LCA

    这里就不在详细介绍了,因为楼下的大佬们基本都是用倍增的,有很详细的解释。

    还是简单说一下吧。简单的来说,就是两个点一起爬树,先让深度比较深的点爬到和低的点一样的位置,然后再开始一起爬,爬到两个点重合就是LCA

    代码如下:

    #include<cmath>
    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    using namespace std;
    int n,m,s,tot=0,cnt=0;
    int head[1000100],nxt[1000100],to[1000100];
    int d[500100],f[30][1000100];
    int read()
    {
        int x=0,flag=0;
        char ch=getchar();
        if(ch=='-') flag=1;
        while(ch<'0'||ch>'9')ch=getchar();
        while(ch>='0'&&ch<='9')x*=10,x+=ch-'0',ch=getchar();
        if(flag) return -x;
        return x;
    }
    void addedge(int x,int y)
    {
        cnt++;
        nxt[cnt]=head[x];
        head[x]=cnt;
        to[cnt]=y;
    }
    void dfs(int u,int dep)//处理出各个点的深度
    {
        d[u]=dep;
        for(int i=head[u];i!=-1;i=nxt[i])
        {
            int v=to[i];
            if(!d[v]) dfs(v,dep+1),f[0][v]=u;
        }
    }
    int LCA(int x,int y)
    {
        int l=0;
        while((1<<l)<=n) l++;
        l--;//l表示的是最大的i为多少,当然,不用求l也可以,只要是一个够大的数像20即可
        if(d[x]<d[y]) swap(x, y);//让x为深度较大的
        for(int i=20;i>=0;i--)
            if(d[y]<=d[x]-(1<<i)) x=f[i][x];//不断爬树,使深度相同
        if(x==y) return x;
        for(int i=20;i>=0;i--)
        {
            if(f[i][x]!=f[i][y])//不同就一起爬树
            {
                x=f[i][x];
                y=f[i][y];
            }
        }
        return f[0][x];
    }
    int main()
    {
        memset(head,-1,sizeof(head));
        n=read(),m=read(),s=read();
        for(int i=1;i<n;i++)
        {
            int x,y;
            x=read(),y=read();
            addedge(x,y);
            addedge(y,x);
        }
        f[0][s]=s;
        dfs(s,1);
        for(int i=1;(1<<i)<=n;i++)
            for(int j=1;j<=n;j++)
                f[i][j]=f[i-1][f[i-1][j]];//f[i][j] 表示 j的2^i 倍祖先,所以f[0][v]就是v的父亲节点u
        for(int i=1;i<=m;i++)
        {
            int l,r;
            l=read(),r=read();
            printf("%d
    ",LCA(l,r));
        }
        return 0;
    }

     三.tarjan求LCA

  • 相关阅读:
    【状压】孤岛营救问题
    【网络流】魔术球问题
    【状压】软件补丁问题
    [网络流]最大流
    [网络流]小M的作物
    简单版AC自动机
    KMP
    sql 检索字符串
    Mysql触发器 :当一个表发生插入或更新时,将数据同步到另一张表中
    sql server触发器 :当一个表发生插入或更新时,将数据同步到另一张表中
  • 原文地址:https://www.cnblogs.com/aprincess/p/11853541.html
Copyright © 2011-2022 走看看