题解:CF1523H Hopping Around the Array

前言

本文同步自洛谷专栏,题目传送门

本题使用的是一种相对复杂的倍增,个人称之为 \(O(k)\) 倍增,特征为求解单个状态复杂度为 \(O(k)\),一般而言,\(k\) 为一个小常数(虽然本题中较大)。

注:普通的倍增,如 LCA,\(fa_{i,u}=fa_{i-1,fa_{i-1,u}}\),认为求解其中一个状态复杂度是 \(O(1)\) 的。

半年前就做过一道类似的题目了,总算在洛谷里找到了一道类似的。

分析

首先要特判 \(l=r\) 的情况。

如果 \(k=0\),则设 \(f_{i,u}\) 表示从 \(u\) 出发走 \(2^i\) 步能到达的下标最大的点,这一部分是容易的。

\(k\ne 0\) 时,将其写入倍增数组中,\(f_{i,j,u}\) 表示从 \(u\) 出发,累计删了 \(j\) 个格子,走了 \(2^i\) 步可达的最远点。

考虑如何倍增,由于多了一维,自然想到额外枚举一次 \(k\),这就是 \(O(k)\) 的由来。

由于不像一些题解定义的最远可达(额外跳一步方便倍增),需要用到 ST 表。

$\red{\text{code}}$
#define rep(i,a,b) for(int i=a;i<=b;i++)
inline void cmax(int &a,int b){(a<b)&&(a=b);}
for(int i=1;i<=D;i++){//rmq 为区间查询最大值,nxt 即为前文的 f
    rep(j,0,30){rep(l,0,j){rep(k,1,n){
        cmax(nxt[i][j][k],rmq(j-l,k,nxt[i-1][l][k]));
    }}}
}

数组大小是 \(O(nk\log{n})\),求解一个位置的复杂度为 \(O(k)\),对于不同的 \(i\),要建立 \(k\) 个 ST 表,预处理总复杂度为 \(O(nk^2\log{n})\)

注意由于 ST 表空间复杂度较高,必须现建现用。如果会四毛子这种科技大抵不用管了。

查询时,从高位(二进制位)向低位考虑该位能否填 \(1\)

\(dp_{i,j}\) 表示已经考虑了第 \(i\) 位及更高位,累计删了 \(j\) 个格子可达的最远点,注意这里的转移也要借助 ST 表。

如果发现 \(dp_{i,k}\) 可以到达 \(r\) 及其右侧的地方,那么就认为改为不可填 \(1\),则和 \(dp_{i+1}\) 保持一致即可。

最终再将答案 \(+1\) 即可。

每次询问要枚举 \(\log{n}\) 位,每一位有一个 \(k^2\) 的转移,询问总复杂度 \(O(qk^2\log{n})\)

$\red{\text{code}}$
#include<bits/stdc++.h>
using namespace std;
#define N 20005
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define gc() (rp1==rp2&&(rp2=(rp1=buf)+fread(buf,1,1<<22,stdin)),*rp1++)

char buf[1<<22],*rp1,*rp2;//单次 O(k) 倍增
int st[20][32][20][N],D,dp[20][32];

inline void cmax(int &a,int b){(a<b)&&(a=b);}
inline int pmax(int &a,int &b){return a<b?b:a;}
inline int LG(int x){return 31^__builtin_clz(x);}

inline int read(){
    int a=0,c=gc();
    while(!isdigit(c)) c=gc();
    while(isdigit(c)) a=10*a+c-'0',c=gc();
    return a;
}

inline int rmq(int p,int k,int l,int r){
    int s=LG(r-l+1);
    return pmax(st[p][k][s][l],st[p][k][s][r-(1<<s)+1]);
}

inline void write(int x){
    int sk[20],top=0;
    do{
        sk[++top]=x%10,x/=10;
    }while(x);
    while(top) putchar('0'+sk[top--]);
    putchar('\n');
}

inline void solve(){
    int l=read(),r=read(),k=read(),res=0;
    if(l==r){write(0);return;}
    memset(dp,0,sizeof(dp));
    for(int i=0;i<=k;i++) dp[D+1][i]=l;
    for(int i=D;i>=0;i--){//逐位确定
        for(int j=0;j<=k;j++){//假设会取
            for(int x=0;j+x<=k;x++){
                cmax(dp[i][j+x],rmq(i,j,l,dp[i+1][x]));
            }
        }
        for(int j=1;j<=k;j++) cmax(dp[i][j],dp[i][j-1]);//前缀 max
        if(dp[i][k]<r){res|=(1<<i);continue;}
        for(int j=0;j<=k;j++) dp[i][j]=dp[i+1][j];//撤销
    }
    write(res+1);
}

int main(){
    // freopen("1.in","r",stdin);
    int n=read(),q=read();D=LG(n);
    for(int i=1;i<=n;i++) st[0][0][0][i]=min(i+read(),n);
    rep(i,1,30){rep(j,1,n){
        st[0][i][0][j]=min(st[0][i-1][0][j]+1,n);
    }}
    for(int i=1;i<=D;i++){//O(nk\log{n}(k+\log{n}))
        rep(j,0,30){rep(l,1,D){rep(k,1,n-(1<<l)+1){
            st[i-1][j][l][k]=pmax(st[i-1][j][l-1][k+(1<<l-1)],st[i-1][j][l-1][k]);
        }}}
        rep(j,0,30){rep(l,0,j){rep(k,1,n){
            cmax(st[i][j][0][k],rmq(i-1,j-l,k,st[i-1][l][0][k]));
        }}}
    }
    rep(j,0,30){rep(l,1,D){rep(k,1,n-(1<<l)+1){
        st[D][j][l][k]=pmax(st[D][j][l-1][k+(1<<l-1)],st[D][j][l-1][k]);
    }}}
    for(int i=1;i<=q;i++) solve();
    return 0;
}

然而该代码空间开销过大,所以可以将询问离线,ST 表仍然是现建现用。

$\red{\text{code}}$
#include<bits/stdc++.h>
using namespace std;
#define N 20005
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define gc() (rp1==rp2&&(rp2=(rp1=buf)+fread(buf,1,1<<22,stdin)),*rp1++)

struct querys{
    int l,r,k,res;
}Q[N];

char buf[1<<22],*rp1,*rp2;//单次 O(k) 倍增
int nxt[20][32][N],st[32][20][N],D,dp[20][N][32];

inline void cmax(int &a,int b){(a<b)&&(a=b);}
inline int pmax(int &a,int &b){return a<b?b:a;}
inline int LG(int x){return 31^__builtin_clz(x);}

inline int read(){
    int a=0,c=gc();
    while(!isdigit(c)) c=gc();
    while(isdigit(c)) a=10*a+c-'0',c=gc();
    return a;
}

inline int rmq(int k,int l,int r){
    int s=LG(r-l+1);
    return pmax(st[k][s][l],st[k][s][r-(1<<s)+1]);
}

inline void write(int x){
    int sk[20],top=0;
    do{
        sk[++top]=x%10,x/=10;
    }while(x);
    while(top) putchar('0'+sk[top--]);
    putchar('\n');
}

int main(){
    int n=read(),q=read();D=LG(n);
    for(int i=1;i<=n;i++) nxt[0][0][i]=min(i+read(),n);
    rep(i,1,30){rep(j,1,n){
        nxt[0][i][j]=min(nxt[0][i-1][j]+1,n);
    }}
    for(int i=1;i<=D;i++){//O(nk\log{n}(k+\log{n}))
        rep(j,0,30){rep(k,1,n) st[j][0][k]=nxt[i-1][j][k];}
        rep(j,0,30){rep(l,1,D){rep(k,1,n-(1<<l)+1){
            st[j][l][k]=pmax(st[j][l-1][k+(1<<l-1)],st[j][l-1][k]);
        }}}
        rep(j,0,30){rep(l,0,j){rep(k,1,n){
            cmax(nxt[i][j][k],rmq(j-l,k,nxt[i-1][l][k]));
        }}}
    }
    for(int i=1,l,r;i<=q;i++){
        l=read(),r=read(),Q[i]={l,r,read(),0};
        for(int j=0;j<=Q[i].k;j++) dp[D+1][i][j]=l;
    }
    for(int i=D;i>=0;i--){
        rep(j,0,30){rep(k,1,n) st[j][0][k]=nxt[i][j][k];}//对当前层建 ST 表
        rep(j,0,30){rep(l,1,D){rep(k,1,n-(1<<l)+1){
            st[j][l][k]=pmax(st[j][l-1][k+(1<<l-1)],st[j][l-1][k]);
        }}}
        rep(y,1,q){rep(j,0,Q[y].k){rep(x,0,Q[y].k-j){//假设会取
            cmax(dp[i][y][j+x],rmq(j,Q[y].l,dp[i+1][y][x]));
        }}}
        for(int y=1;y<=q;y++){
            for(int j=1;j<=Q[y].k;j++) cmax(dp[i][y][j],dp[i][y][j-1]);//前缀 max
            if(dp[i][y][Q[y].k]<Q[y].r){Q[y].res|=(1<<i);continue;}
            for(int j=0;j<=Q[y].k;j++) dp[i][y][j]=dp[i+1][y][j];//撤销
        }
    }
    for(int i=1;i<=q;i++) write(Q[i].l==Q[i].r?0:Q[i].res+1);
    return 0;
}

则本题总时间复杂度 \(O((n+q)k^2\log{n})\),足以通过。

如果被卡常了,可以考虑更换下标枚举顺序,连续访问往往效率更高。

posted @ 2026-06-05 10:22  Wxb2010  阅读(13)  评论(0)    收藏  举报