深夜题解
这道题的正解应该是用动态树,但像我这种菜鸡肯定没有学过啊
考虑用分块来莽一下,因为这道题分块的复杂度为 O(m√n) 是完全可以接受的
首先分块常规操作,将整个数列等分为 √n 块,记录每块的左右区间,和每个位置属于哪一块。
然后对于每一个位置 i,求出 to[i]:i 位置通过不断跳跃,跳出本块之后的位置。step[i]:i 位置跳出本块需要的最小次数。
然后对于每一次查询 x,通过不断的迭代 x=to[x],累加 step[x],就可以在 O(√n) 的时间复杂度内求出答案。
对于每一次修改 x 位置,我们可以暴力的调整 x 所在的整块的所有位置 to 和 step,这里的时间复杂度也是 O(√n),而其他的位置因为我们在计算 to 和 step 时的考虑范围只在该位置所处的块内,所有不受修改的影响。
=====================分界线=====================
仔细想一下,如果不修改,那么 O(1) 的查询方法应该谁都能写得出来,但如果在这个方法的基础上添加修改操作,那么这个修改的时间复杂度就是 O(n) 的了,放到整个题,时间复杂度就变成 O(mn) 了。
这里用上了分块,虽然查询的时间复杂度变成了 O(√n),但同样修改的时间复杂度也降低到了 O(√n),那么解决问题的总复杂度就是降低到了 O(m√n) 了,相当于我们通过分块,均摊了查询和修改的复杂度,这就是分块思想的核心目的所在吧。
+++++++++++++++++++康康代码++++++++++++++++++++++
#include <iostream> #include <stdio.h> #include <math.h> #include <stdlib.h> #include <string.h> #define MAXN 200010 using namespace std; int n,m,t; int a[MAXN],L[MAXN],R[MAXN],pos[MAXN],step[MAXN],to[MAXN]; int read(){ int x=0,f=1;char c=getchar(); while(c<'0'||c>'9') {if(c=='-')f=-1;c=getchar();} while(c>='0'&&c<='9') {x=x*10+c-'0';c=getchar();} return x*f; } int main(){ n=read(); for(int i=1;i<=n;i++) a[i]=read(); t=sqrt(n); for(int i=1;i<=t;i++) L[i]=(i-1)*t+1,R[i]=i*t; if(R[t]<n) t++,L[t]=R[t-1]+1,R[t]=n; for(int i=1;i<=t;i++) for(int j=L[i];j<=R[i];j++) pos[j]=i; for(int i=n;i>=1;i--){ to[i]=i+a[i];step[i]=1; if(to[i]<=R[pos[i]]) step[i]+=step[to[i]],to[i]=to[to[i]]; } m=read(); int opt,x,y; while(m--){ opt=read();x=read();x++; if(opt==1){ int ans=0,now=x; while(now<=n) ans+=step[now],now=to[now]; printf("%d ",ans); } else if(opt==2){ y=read();a[x]=y; for(int i=x;i>=L[pos[x]];i--){ to[i]=i+a[i];step[i]=1; if(to[i]<R[pos[i]]) step[i]+=step[to[i]],to[i]=to[to[i]]; } } } return 0; }