最近公共祖先(LCA)&&RMQ问题(ST表)(2019.7.29训练)

P3379 【模板】最近公共祖先(LCA)

LCA(Lowest Common Ancestor)模板,注释写得很详细了 (大佬请自动忽略我写的冗长的注释)

#include <bits/stdc++.h>
using namespace std;
const int N=5e5+10;
int n,m,x,y,s,v,cnt,f[N][21],dep[N],head[N];//f[i][j]表示i的2^j祖先,也就是i向上跳2^j步的点。dep表示深度。
struct node
{
    int to,next;
}e[N<<1];//无向图开两倍大小
void add(int x,int y)//链式前向星存边
{
    e[cnt].to=y;
    e[cnt].next=head[x];
    head[x]=cnt++;
}
void dfs(int u,int father)//预处理得到f数组,u表示当前搜索到的点,father表示u的父节点
{
    dep[u]=dep[father]+1;//u的父节点深度+1就是u的深度
    f[u][0]=father;//u向上跳2^0步(1步),为father点
    for(int i=1;i<=20;i++)//u与其祖先的距离最大不超过2^20
        f[u][i]=f[f[u][i-1]][i-1];//dp的思想:u向上跳2^i步相当于u向上跳2^(i-1)步,再向上跳2^(i-1)步
    for(int i=head[u];i!=-1;i=e[i].next)//遍历与u相连的点
    {
        v=e[i].to;
        if(v!=father)dfs(v,u);//如果当前遍历的点v不是father点,则可以向下继续搜索
    }
}
int lca(int x,int y)//求最近公共祖先(LCA)
{
    if(dep[x]<dep[y])swap(x,y);//保证x的深度大于等于y的深度
    for(int i=20;i>=0;i--)//依次尝试x向上跳2^logn,...2^1,2^0步(logn最大不超过20)
    {
        if(dep[f[x][i]]>=dep[y])x=f[x][i];//让x向上跳,直到与y同深度
        if(x==y)return x;//若x跳到与y同深度时与y重合,则说明x,y的最近公共祖先就是原来的y(也是现在的x)
    }
    for(int i=20;i>=0;i--)//依次尝试x,y向上跳2^logn,...2^1,2^0步(logn最大不超过20)
    {
        if(f[x][i]!=f[y][i])//若x,y同时向上跳2^i步后不重合,此时x,y可以同时向上跳
        {
            x=f[x][i];
            y=f[y][i];
        }
    }//结束循环时,必定有x与y的父节点相同,即答案为f[x][0]=f[y][0]
    return f[x][0];
}
int main()
{
    ios::sync_with_stdio(false);
    cin>>n>>m>>s;
    memset(head,-1,sizeof(head));
    for(int i=1;i<=n-1;i++)
    {
        cin>>x>>y;
        add(x,y);
        add(y,x);//无向树,反向也要加边
    }
    dfs(s,0);//从起点s开始搜索,设起点的父节点为0
    while(m--)
    {
        cin>>x>>y;
        printf("%d\n",lca(x,y));
    }
    return 0;
}

SP14932 LCA - Lowest Common Ancestor

照着LCA模板写咯。

#include <bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int n,m,x,y,t,q,v,cnt,f[N][21],dep[N],head[N];
struct node
{
    int to,next;
}e[N<<1];
void add(int x,int y)
{
    e[cnt].to=y;
    e[cnt].next=head[x];
    head[x]=cnt++;
}
void dfs(int u,int father)
{
    dep[u]=dep[father]+1;
    f[u][0]=father;
    for(int i=1;i<=20;i++)
        f[u][i]=f[f[u][i-1]][i-1];
    for(int i=head[u];i!=-1;i=e[i].next)
    {
        v=e[i].to;
        if(v!=father)dfs(v,u);
    }
}
int lca(int x,int y)
{
    if(dep[x]<dep[y])swap(x,y);
    for(int i=20;i>=0;i--)
    {
        if(dep[f[x][i]]>=dep[y])x=f[x][i];
        if(x==y)return x;
    }
    for(int i=20;i>=0;i--)
    {
        if(f[x][i]!=f[y][i])
        {
            x=f[x][i];
            y=f[y][i];
        }
    }
    return f[x][0];
}
int main()
{
    ios::sync_with_stdio(false);
    cin>>t;
    for(int cas=1;cas<=t;cas++)
    {
        memset(head,-1,sizeof(head));
        cnt=0;
        cin>>n;
        for(x=1;x<=n;x++)
        {
            cin>>m;
            while(m--)
            {
                cin>>y;
                add(x,y);
                add(y,x);
            }
        }
        dfs(1,0);
        printf("Case %d:\n",cas);
        cin>>q;
        while(q--)
        {
            cin>>x>>y;
            printf("%d\n",lca(x,y));
        }
    }
    return 0;
}

P3865 【模板】ST表

RMQ问题,Range Maximum(Minimum) Query,询问某个区间内的最大值或最小值。
显然,可以用线段树解决这一问题,AC代码(用时1.61s):

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n,m,x,y,a[N],tr[4*N];
void build(int i,int l,int r)
{
    if(l==r)
    {
        tr[i]=a[l];
        return;
    }
    int mid=l+r>>1;
    build(2*i,l,mid);
    build(2*i+1,mid+1,r);
    tr[i]=max(tr[2*i],tr[2*i+1]);
}
int query(int i,int l,int r,int x,int y)
{
    if(l>y||r<x)return 0;
    if(l>=x&&r<=y)return tr[i];
    int mid=l+r>>1;
    return max(query(2*i,l,mid,x,y),query(2*i+1,mid+1,r,x,y));
}
int main()
{
    ios::sync_with_stdio(false);
    cin>>n>>m;
    for(int i=1;i<=n;i++)
        cin>>a[i];
    build(1,1,n);
    while(m--)
    {
        cin>>x>>y;
        printf("%d\n",query(1,1,n,x,y));
    }
    return 0;
}

没有修改操作的条件下,则可使用ST算法,相比于线段树,它的运行速度更快,可以做到O(nlogn)的预处理,O(1)回答每次询问。
ST算法,AC代码(用时989ms):

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n,m,x,y,k,a[N],lg[N],f[N][21];//lg[i]表示log2(i)向下取整的值,f[i][j]表示从i开始的连续2^j个数的最大值,即区间[i,i+2^j-1]的最大值
int main()
{
    ios::sync_with_stdio(false);
    cin>>n>>m;
    lg[0]=-1;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        f[i][0]=a[i];//从i开始的连续2^0个数的最大值就等于a[i]本身
        lg[i]=lg[i/2]+1;//预处理log2(i),因为cmath库中自带的函数log2(x)速度较慢
    }
    for(int j=1;j<=20;j++)//O(nlogn)的预处理
        for(int i=1;i+(1<<j)-1<=n;i++)//i+2^j-1不能超过边界n
        f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]);//把[i,i+2^j-1]分成左区间[i,i+2^(j-1)-1]和右区间[i+2^(j-1),i+2^j-1],取较大值
    while(m--)
    {
        cin>>x>>y;
        k=lg[y-x+1];//k为方程2^k<=y-x+1的解的最大值,即log2(y-x+1)向下取整
        printf("%d\n",max(f[x][k],f[y-(1<<k)+1][k]));//取左区间[x,x+2^k-1]和右区间[y-2^k+1,y]的较大值(左右区间有重合部分)
    }
    return 0;
}
发布了72 篇原创文章 · 获赞 91 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/ljw_study_in_CSDN/article/details/97806830