[bzoj2002]弹飞绵羊

题目大意

有N个点,每个点有一个系数a[i],你处于位置i可以走到i+a[i],若i+a[i]>n则你走出了地图。现M个操作有两种:1、把a[j]修改为k。2、询问你位于点j时,需要走多少部走出地图。n<=2*10^5,m<=10^5。

LCT裸题

我们可以转化为如下问题:1、将x的父亲设为y。2、询问x的深度。
那就变成了LCT裸题,为了支持询问操作只需要维护size即可。

#include<cstdio>
#include<algorithm>
#include<stack>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
const int maxn=200000+10;
stack<int> sta;
int father[maxn],size[maxn],pp[maxn],tree[maxn][2],a[maxn];
bool bz[maxn];
int i,j,k,l,t,n,m;
void update(int x){
    size[x]=size[tree[x][0]]+size[tree[x][1]]+1;
}
int pd(int x){
    return tree[father[x]][1]==x;
}
void rotate(int x){
    int y=father[x],z=pd(x);
    tree[y][z]=tree[x][1-z];
    if (tree[x][1-z]) father[tree[x][1-z]]=y;
    father[x]=father[y];
    if (father[y]) tree[father[y]][pd(y)]=x;
    tree[x][1-z]=y;
    father[y]=x;
    update(y);
    update(x);
    if (pp[y]) pp[x]=pp[y],pp[y]=0;
}
void clear(int x){
    if (bz[x]){
        bz[x]=0;
        if (tree[x][0]) bz[tree[x][0]]^=1;
        if (tree[x][1]) bz[tree[x][1]]^=1;
        swap(tree[x][0],tree[x][1]);
    }
}
void remove(int x,int y){
    while (x!=y){
        sta.push(x);
        x=father[x];
    }
    while (!sta.empty()){
        clear(sta.top());
        sta.pop();
    }
}
void splay(int x,int y){
    remove(x,y);
    while (father[x]!=y){
        if (father[father[x]]!=y)
            if (pd(x)==pd(father[x])) rotate(father[x]);else rotate(x);
        rotate(x);
    }
}
void access(int x){
    int y;
    splay(x,0);
    father[tree[x][1]]=0;
    if (tree[x][1]) pp[tree[x][1]]=x;
    tree[x][1]=0;
    update(x);
    while (pp[x]){
        y=pp[x];
        splay(y,0);
        father[tree[y][1]]=0;
        if (tree[y][1]) pp[tree[y][1]]=y;
        tree[y][1]=x;
        father[x]=y;
        pp[x]=0;
        update(y);
        splay(x,0);
    }
}
void makeroot(int x){
    access(x);
    splay(x,0);
    bz[x]^=1;
}
void cut(int x,int y){
    access(x);
    splay(y,0);
    pp[y]=0;
}
void link(int x,int y){
    makeroot(x);
    splay(y,0);
    pp[y]=x;
    makeroot(n+1);
}
int main(){
    scanf("%d",&n);
    fo(i,1,n){
        scanf("%d",&a[i]);
        j=(i+a[i]>n)?n+1:i+a[i];
        link(j,i);
    }
    scanf("%d",&m);
    while (m--){
        scanf("%d%d",&t,&j);
        j++;
        if (t==1){
            access(j);
            splay(j,0);
            printf("%d\n",size[tree[j][0]]);
        }
        else{
            scanf("%d",&k);
            l=(j+a[j]>n)?n+1:j+a[j];
            cut(l,j);
            a[j]=k;
            l=(j+a[j]>n)?n+1:j+a[j];
            link(l,j);
        }
    }
}

不过我们有更好的方法。

分块大法好

我们分块后,对每一个元素维护两个东西:b[i]表示i一直跳跳出自己所在块后跳到了哪个点,d[i]表示i跳出自己所在块的所需步数。有了这两个数组就可以进行询问,而修改操作暴力重建整个块就好了。

#include<cstdio>
#include<algorithm>
#include<cmath>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
const int maxn=200000+10;
int a[maxn],b[maxn],d[maxn];
int i,j,k,l,t,n,m,ans,c;
int main(){
    scanf("%d",&n);
    c=floor(sqrt(n))+1;
    fo(i,1,n) scanf("%d",&a[i]);
    fd(i,n,1){
        k=min(((i-1)/c+1)*c,n);
        if (i+a[i]>k) d[i]=1,b[i]=i+a[i];
        else d[i]=d[i+a[i]]+1,b[i]=b[i+a[i]];
    }
    scanf("%d",&m);
    while (m--){
        scanf("%d",&t);
        if (t==1){
            scanf("%d",&j);
            j++;
            ans=0;
            while (j<=n){
                ans+=d[j];
                j=b[j];
            }
            printf("%d\n",ans);
        }
        else{
            scanf("%d%d",&j,&k);
            j++;
            a[j]=k;
            k=min(((j-1)/c+1)*c,n);
            fd(i,k,(j-1)/c*c+1){
                if (i+a[i]>k) d[i]=1,b[i]=i+a[i];
                else d[i]=d[i+a[i]]+1,b[i]=b[i+a[i]];
            }
        }
    }
}

你可能感兴趣的:([bzoj2002]弹飞绵羊)