洛谷 P3203 [HNOI2010]弹飞绵羊(分块)

传送门

深夜题解

这道题的正解应该是用动态树,但像我这种菜鸡肯定没有学过啊

考虑用分块来莽一下,因为这道题分块的复杂度为 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;
}
原文地址:https://www.cnblogs.com/BakaCirno/p/11717500.html