洛谷 P3202 弹飞绵羊 题解
做了这道题我才知道,所谓的分块并不只局限于一个数组的拆分形式,它更多时候代表了一种思想。
比如莫队就利用了分块的思想,虽然很多莫队题并没有出现分块。
我就被这题的分块标签吓住了,拼命地去想怎么开一个n1/2的数组,结果一无所获,直到看完题解,才恍然大悟。
我做的题还是太少了啊。
做法
首先,对每个弹力装置,预处理出跳出它所在的这个分块需要多少步(tim),会落到哪里(nxt)。
合理的数据中弹力系数至少是1,所以一个装置查询这个最多跳n1/2次。
void get_nxt(int x){//x是装置的编号
int now=x,now_b=get_pos(x);//get_pos是我写的一个返回某点分块编号的函数,更通用的写法是预处理出每个位置所在的分块编号然后直接调用
while(get_pos(now)==now_b&&now<=n)//暴力模拟往后跳的过程 tim[x]++,now+=a[now]; nxt[x]=now;//最后这里now跳出了分块,就是第一个跳出此分块的落点 }
其实这一步可以进行优化,我忽然想到可以借鉴线性求逆元的思想(其实二者啥关系都没有)。
我们从后往前处理这两个东西。
对于弹力装置x,令它跳一步的落点是y。
如果y不在x块内或者y已经不在n的范围内了,那x跳出分块只要一步,落点就是y。
如果y在块内,那么x跳出分块必然经过y,那x跳出分块的落点也必然是y跳出分块的落点。
而x跳出分块的路径也就是y跳出分块的路径加上x跳到y这一步。
这么处理时间复杂度为O(1)。
void get_nxt(int x){ if(get_pos(x+a[x])>get_pos(x)||x+a[x]>n) tim[x]=1,nxt[x]=x+a[x]; else tim[x]=tim[x+a[x]]+1,nxt[x]=nxt[x+a[x]]; }
接下来处理修改操作。
如果一个装置x的弹力系数被修改了,那么只会影响到与x处在同一块内在x之前的弹力装置(下图橙色),对于上一分块的弹力装置,并不构成影响(下图蓝色)。
最多更新n1/2个弹力装置。
void update(int x,int y){ a[x]=y,tim[x]=0,get_nxt(x);//更新x的相关性质 for(int i=x-1;i>=(get_pos(x)-1)*len+1;i--)//依然从后往前修改 if(i+a[i]<=n&&get_pos(i+a[i])<=get_pos(i))//一些防止越界的性质 tim[i]=tim[i+a[i]]+1,nxt[i]=nxt[i+a[i]]; }
接下来处理查询操作。
往后一个分块一个分块地跳并统计次数。
如果跳出该分块的落点超出了n,那就不能直接跳过这个分块。
一步一步跳,直到落点超出n,统计次数。
两种跳跃方式下跳跃次数都是n1/2级别的。
int query(int x){ int ans=0; while(nxt[x]<=n)//一个分块一个分块地跳 ans+=tim[x],x=nxt[x]; while(x<=n)//一步一步跳 ans++,x=x+a[x]; return ans; }
这样就完成了这道题,预处理时间复杂度O(n),查询和修改时间复杂度都是O(n3/2)的,可以通过本题。
#include<bits/stdc++.h> using namespace std; const int h=200010; int n,m; int a[h]; int nxt[h],tim[h]; int len; int get_pos(int x){ return (x-1)/len+1; } void get_nxt(int x){ if(get_pos(x+a[x])>get_pos(x)||x+a[x]>n) tim[x]=1,nxt[x]=x+a[x]; else tim[x]=tim[x+a[x]]+1,nxt[x]=nxt[x+a[x]]; } void update(int x,int y){ a[x]=y,tim[x]=0,get_nxt(x); for(int i=x-1;i>=(get_pos(x)-1)*len+1;i--) if(i+a[i]<=n&&get_pos(i+a[i])<=get_pos(i)) tim[i]=tim[i+a[i]]+1,nxt[i]=nxt[i+a[i]]; } int query(int x){ int ans=0; while(nxt[x]<=n) ans+=tim[x],x=nxt[x]; while(x<=n) ans++,x=x+a[x]; return ans; } int main(){ scanf("%d",&n); len=sqrt(n); for(int i=1;i<=n;i++) scanf("%d",&a[i]); for(int i=n;i>=1;i--) get_nxt(i); scanf("%d",&m); int op,x,y; for(int i=1;i<=m;i++){ scanf("%d",&op); if(op==1) scanf("%d",&x),printf("%d\n",query(x+1)); else scanf("%d%d",&x,&y),update(x+1,y); } return 0; }
代码长度很短,难度不大,重点在思维。
还有一种LCT的解法,感兴趣的话可以自行了解,本人能力有限,不予涉猎。