ST表(Sparse table)


可重复贡献问题(如rmq问题):

是指对于运算opt满足x opt x=x,则对应的区间询问就是一个可重复贡献问题。rmq涉及的运算就是max以及min,同时,对于gcd运算(最大公约数)、按位与、按位或等运算,rmq都能实现对应的静态区间查询。


基于ST倍增的rmq解决算法

ST表构造思路:

  • 使用二维数组,M[i][j]表示以i开始,长度为$$2^j$$区间的最值。

  • 使用dp状态转移求解st表,相当于[i,i+2^j-1]区间最值相当于两个子区间[i,i+2^{j-1}-1]和st值与[i+2^{j-1},i+2^{j}-1]的st值的最值,分解为子问题求解。

代码实现:

void ST(void)
{
    for(int i=1;i<=n;i++)
    st[i][0]=a[i];
    for(ll j=1;(1<<j)<=n;j++)
    for(ll i=1;i+(1<<j)-1<=n;i++)
    {
        st[i][j]=max(st[i][j-1],st[i+(1<<(j-1))][j-1]);
    }
}

查询操作:

若要查询区间[i,j]的最值,设k=\lfloor log_2(j-i+1)\rfloor,可证明2^k一定大于等于该区间长度的一半,则

rmq(i,j)=max {M[i][k],M[j-2^k+1][k]}

代码实现:

ll query(int l,int r)
{
    int len=r-l+1,k;
    for(k=0;(1<<k)<=len;k++);
    k--;
    return max(st[l][k],st[r-(1<<k)+1][k]);
}

完整实现:(以【模板】ST 表 && RMQ 问题 - 洛谷为例)

#include<bits/stdc++.h>
#define maxn 1000
#define INF 0x3f3f3f3f
#define ll long long
using namespace std;
ll n,m,a[maxn];
ll st[maxn][maxn];
void ST(void)
{
    for(int i=1;i<=n;i++)
    st[i][0]=a[i];
    for(ll j=1;(1<<j)<=n;j++)
    for(ll i=1;i+(1<<j)-1<=n;i++)
    {
        st[i][j]=max(st[i][j-1],st[i+(1<<(j-1))][j-1]);
    }
}
ll query(int l,int r)
{
    int len=r-l+1,k;
    for(k=0;(1<<k)<=len;k++);
    k--;
    return max(st[l][k],st[r-(1<<k)+1][k]);
}
int main()
{
    scanf("%lld%lld",&n,&m);
    for(int i=1;i<=n;i++)
    scanf("%lld",&a[i]);
    ST();
    for(int i=1;i<=m;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        printf("%lld\n",query(x,y));
    }
    system("pause");
}

基于ST表的LCA解决思路

  • 算法思路:

  1. 首先,若两点u,v深度不同,将深度更深的点通过循环操作跳至与另一个点深度相同,判断是否为同一个点,若是直接输出答案,若非,进入下一步。

  2. 根据深度选择向上跳跃的步长,每次选择2^i,i在每次循环后递减,目的是找出两者的最近公共祖先,若第一次就发现两点跳到了同一点,则不着急跳,进行下一轮循环,若下一轮循环两点不在同一点,则往上跳,因为此时说明,lca一定是树结构上上一轮循环观测的点与此时跳跃的点之间的点,此时不断跃进直到走到lca的子节点处停下;

  3. 输出father[u]或father[v]

  • 代码实现:

预处理:

void Pre(void)
{
    for(int i=0;i<=n;i++)
    for(int j=0;(1<<j)<=n;j++)//一个节点可能有最多n个祖先
    acs[i][j]=-1;
    for(int i=1;i<=n;i++)
    acs[i][0]=f[i];
    for(int j=1;(1<<j)<=n;j++)
    for(int i=1;i<=n;i++)
    if(acs[i][j-1]!=-1)
    acs[i][j]=acs[acs[i][j-1]][j-1];
}

LCA:

int LCA(int u,int v)
{
    int tem,lg;
    if(d[u]<d[v])
    {
        tem=u;
        u=v;
        v=tem;
    }
    for(lg=0;(1<<lg)<=d[u];lg++);
    lg--;
    for(int i=lg;i>=0;i--)
    if((1<<i)<=d[u]-d[v])u=acs[u][i];//跳至两点深度相同
    if(u==v) return u;
    for(int i=lg;i>=0;i--)
    if(acs[u][i]!=-1&&acs[u][i]!=acs[v][i])//继续跳至lca的子节点处
    u=acs[u][i],v=acs[v][i];
    return f[u];
}

完整实现:

#include<bits/stdc++.h>
#define ll long long
#define maxn 1000
#define INF 0x3f3f3f3f
using namespace std;
int n,m,s;
int head[maxn];
struct Edge{
    int to;
    int next;
}edge[maxn];
int tot=0,d[maxn],f[maxn],acs[maxn][maxn];
void addedge(int u,int v)
{
    edge[++tot].to=v;
    edge[tot].next=head[u];
    head[u]=tot;
}
void dfs(int x,int fa)
{
    f[x]=fa;
    d[x]=d[fa]+1;
    for(int i=head[x];~i;i=edge[i].next)
    {
        int to=edge[i].to;
        if(to==fa)continue;
        dfs(to,x);
    }
}
void Pre(void)
{
    for(int i=0;i<=n;i++)
    for(int j=0;(1<<j)<=n;j++)//一个节点可能有最多n个祖先
    acs[i][j]=-1;
    for(int i=1;i<=n;i++)
    acs[i][0]=f[i];
    for(int j=1;(1<<j)<=n;j++)
    for(int i=1;i<=n;i++)
    if(acs[i][j-1]!=-1)
    acs[i][j]=acs[acs[i][j-1]][j-1];
}
int LCA(int u,int v)
{
    int tem,lg;
    if(d[u]<d[v])
    {
        tem=u;
        u=v;
        v=tem;
    }
    for(lg=0;(1<<lg)<=d[u];lg++);
    lg--;
    for(int i=lg;i>=0;i--)
    if((1<<i)<=d[u]-d[v])u=acs[u][i];
    if(u==v) return u;
    for(int i=lg;i>=0;i--)
    if(acs[u][i]!=-1&&acs[u][i]!=acs[v][i])
    u=acs[u][i],v=acs[v][i];
    return f[u];
}
int main()
{
    scanf("%d%d%d",&n,&m,&s);
    for(int i=0;i<=n;i++)
    head[i]=-1;
    for(int i=1;i<=n-1;i++)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        addedge(u,v);
        addedge(v,u);//链式前向星无向边加两次
    }
    dfs(s,s);
    Pre();
    for(int i=1;i<=m;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        printf("%d\n",LCA(x,y));
    }
    system("pause");
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值