[BZOJ 2002][Hnoi2010]Bounce 弹飞绵羊:分块|LCT

3 篇文章 0 订阅
2 篇文章 0 订阅

点击这里查看原题

分块思路:记录在块内需要跳几次进入别的块以及进入别的块的位置。

/*
User:Small
Language:C++
Problem No.:2002
*/
#include<bits/stdc++.h>
#define ll long long
#define inf 999999999
using namespace std;
const int M=2e5+5;
int n,m,k[M],pos[M],l[1005],r[1005],st[M],pt[M];
int cal(int x){
    int res=0;
    while(1){
        res+=st[x];
        if(!pt[x]) break;
        x=pt[x];
    }
    return res;
}
int main(){
    freopen("data.in","r",stdin);//
    scanf("%d",&n);
    int t=sqrt(n),cnt=(n-1)/t+1;
    for(int i=1;i<=n;i++){
        scanf("%d",&k[i]);
        pos[i]=(i-1)/t+1;
    }
    for(int i=1;i<=cnt;i++){
        l[i]=(i-1)*t+1;
        r[i]=i*t;
    }
    r[cnt]=n;
    for(int i=n;i;i--){
        if(i+k[i]>n) st[i]=1;
        else if(pos[i]==pos[i+k[i]]){
            st[i]=st[i+k[i]]+1;
            pt[i]=pt[i+k[i]];
        }
        else{
            st[i]=1;
            pt[i]=i+k[i];
        }
    }
    scanf("%d",&m);
    while(m--){
        int op,x;
        scanf("%d%d",&op,&x);
        x++;
        if(op==1) printf("%d\n",cal(x));
        else{
            scanf("%d",&k[x]);
            for(int i=x;i>=l[pos[x]];i--){
                if(pos[i]==pos[i+k[i]]){
                    st[i]=st[i+k[i]]+1;
                    pt[i]=pt[i+k[i]];
                }
                else{
                    st[i]=1;
                    pt[i]=i+k[i];
                }
            }
        }
    }
    return 0;
}

LCT:模板题,用本题来学习LCT,具体功能有详细注释

/*
User:Small
Language:C++
Problem No.:2002
*/
#include<bits/stdc++.h>
#define ll long long
#define inf 999999999
using namespace std;
const int M=2e5+5;
int n,m,fa[M],ch[M][2],a[M],siz[M],stk[M];
bool rev[M];
bool get(int x){
    return ch[fa[x]][1]==x;
}
bool is_root(int x){
    return ch[fa[x]][0]!=x&&ch[fa[x]][1]!=x;
}
void pushup(int rt){
    siz[rt]=1;
    if(ch[rt][0]) siz[rt]+=siz[ch[rt][0]];
    if(ch[rt][1]) siz[rt]+=siz[ch[rt][1]];
}
void pushdown(int rt){
    if(rev[rt]){
        rev[rt]^=1;
        rev[ch[rt][0]]^=1;
        rev[ch[rt][1]]^=1;
        swap(ch[rt][0],ch[rt][1]);
    }
}
void rotate(int x){
    int y=fa[x],z=fa[y],side=get(x);
    if(!is_root(y)){
        ch[z][ch[z][1]==y]=x;
    }
    fa[x]=z;
    ch[y][side]=ch[x][side^1];
    fa[ch[y][side]]=y;
    ch[x][side^1]=y;
    fa[y]=x;
    pushup(y);
    pushup(x);
}
void splay(int x){
    int tp=0;
    stk[++tp]=x;
    for(int t=x;!is_root(t);t=fa[t])
        stk[++tp]=fa[t];
    for(int i=tp;i;i--) pushdown(stk[i]);
    while(!is_root(x)){
        int y=fa[x];
        if(!is_root(y))
            rotate(get(y)==get(x)?y:x);
        rotate(x);
    }
}
void access(int x){
    /*
    目的:将x的重边切断,并将x到根的路径上所有的边都搞成重边。
    具体实现:根据辅助树按照深度为关键字的性质,重建一颗splay。
            不断地将一个结点的父亲转到根,然后把这个结点接到它父亲的右儿子。 
    */
    int t=0;
    while(x){
        splay(x);
        ch[x][1]=t;
        t=x;
        x=fa[x];
    }
}
void reverse(int x){
    /*
    目的:将原树中的x结点转到根。
    具体实现:因为原树是虚树,所以在原树中进行变换实际上是在辅助树中进行变换。
            首先Access一个点,再将这个点在辅助树中转到根。又是根据辅助书按照深度为关键字的性质,
            将这个点所在的splay树反转,实际上改变了深度的关系,也就是实现的原树的换根。 
    */
    access(x);
    splay(x);
    rev[x]^=1;
}
void cut(int x,int y){
    /*
    目的:将两个连通的点不连通,换句话说,把一棵树拆成两棵树。
    具体实现:首先进行Reverse操作,在原树中将一个点转到那个点所在的树的根。
            然后Access另外一个点,把另外一个点在辅助树中转到根。由于这两个点原先是连通的,
            那么进行Access操作之后两个点在辅助树中一定是一个位于根,一个位于根的左儿子(深度)。
            所以在辅助树中把这个边砍掉就行了。
    */
    reverse(x);
    access(y);
    splay(y);
    fa[x]=ch[y][0]=0;
}
void link(int x,int y){
    /*
    目的:将两个不连通的点连通。换句话来说,合并或扔到一颗树里。
    具体实现:首先进行Reverse操作,在原树中将一个点转到那个点所在的树的根。
            然后将这个转到根的点的father接到另外一个点上。可以进行一次splay来update。 
    */
    reverse(x);
    fa[x]=y;
    splay(x);
}
int main(){
    freopen("data.in","r",stdin);//
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        int x;
        scanf("%d",&x);
        fa[i]=a[i]=min(n+1,i+x);
        siz[i]=1;
    }
    siz[n+1]=1;
    scanf("%d",&m);
    while(m--){
        int op,x,y;
        scanf("%d%d",&op,&x);
        x++;
        if(op==1){
            reverse(n+1);
            access(x);
            splay(x);
            printf("%d\n",siz[ch[x][0]]);
        }
        else{
            scanf("%d",&y);
            cut(x,a[x]);
            a[x]=min(n+1,x+y);
            link(x,a[x]);
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值