题解: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})\),足以通过。
如果被卡常了,可以考虑更换下标枚举顺序,连续访问往往效率更高。

浙公网安备 33010602011771号