洛谷 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的解法,感兴趣的话可以自行了解,本人能力有限,不予涉猎。

 

posted on 2022-11-14 21:36  timedrop  阅读(12)  评论(0编辑  收藏  举报