[Link-Cut-Tree][BZOJ2002]弹飞绵羊

题面:

Description:

某天,Lostmonkey发明了一种超级弹力装置,为了在他的绵羊朋友面前显摆,他邀请小绵羊一起玩个游戏。游戏一开始,Lostmonkey在地上沿着一条直线摆上 n n 个装置,每个装置设定初始弹力系数ki,当绵羊达到第 i i 个装置时,它会往前弹ki步,达到第 i+ki i + k i 个装置,若不存在第 i+ki i + k i 个装置,则绵羊被弹飞。绵羊想知道当它从第 i i 个装置起步时,被弹几次后会被弹飞。为了使得游戏更有趣,Lostmonkey可以修改某个弹力装置的弹力系数,任何时候弹力系数均为正整数。

Input:

第一行包含一个整数n,表示地上有 n n 个装置,装置的编号从0 n1 n − 1
接下来一行有 n n 个正整数,依次为那n个装置的初始弹力系数。
第三行有一个正整数 m m
接下来m行每行至少有两个数 i,j i , j ,若 i=1 i = 1 ,你要输出从 j j 出发被弹几次后被弹飞,若i=2则还会再输入一个正整数 k k ,表示第j个弹力装置的系数被修改成 k k

Output:

对于每个i=1的情况,你都要输出一个需要的步数,占一行。

Sample Input:

4
1 2 1 1
3
1 1
2 1 1
1 1

Sample Output:

2
3

Hint:

对于20%的数据 n,m10000 n , m ≤ 10000 ;
对于100%的数据 n200000,m100000 n ≤ 200000 , m ≤ 100000 ;


1.如何建树?
  • 若从这个点 x x 会被弹飞,连边(x,n+1)
  • 若从这个点 x x 不会被弹飞,连边(x,x+kx)

根为 n+1 n+1

以样例做例子:

2.如何询问?

哈?
由于splay是按深度关键字排序,所以根的左子树的大小就是要被弹几次了呀。

3.如何修改?

把原来的边删了在连新的不就完了吗……


代码:
#include<iostream>
#include<cstdio>
using namespace std;
int ch[200002][2],fa[200002],siz[200002],num[200002],lazr[200002],cnt,n,q;
inline unsigned rd(){
    unsigned re=0;
    char ch=getchar();
    while(ch<'0'||ch>'9')ch=getchar();
    while(ch>='0'&&ch<='9'){
        re=re*10+ch-'0';
        ch=getchar();
    }
    return re;
}
inline bool isroot(int bt){return ch[fa[bt]][0]!=bt&&ch[fa[bt]][1]!=bt;}
inline int drct(int bt){return ch[fa[bt]][1]==bt;}
inline void pushup(int bt){siz[bt]=siz[ch[bt][0]]+siz[ch[bt][1]]+1;}
inline void reverse(int bt){swap(ch[bt][0],ch[bt][1]);lazr[bt]^=1;}
inline void pd(int bt){
    if(lazr[bt]){
        if(ch[bt][0])reverse(ch[bt][0]);
        if(ch[bt][1])reverse(ch[bt][1]);
        lazr[bt]=0;
    }
}
inline void pushdown(int u){
    if(!isroot(u))pushdown(fa[u]);
    pd(u);
}
inline void rotate(int u){
    int f=fa[u],g=fa[f],c=drct(u);
    if(!isroot(f))ch[g][drct(f)]=u;
    fa[u]=g;
    ch[f][c]=ch[u][c^1];
    if(ch[f][c])fa[ch[f][c]]=f;
    ch[u][c^1]=f;
    fa[f]=u;
    pushup(f);
    pushup(u);
}
void splay(int u){
    pushdown(u);
    while(!isroot(u)){
        if(!isroot(fa[u]))rotate(drct(fa[u])==drct(u)?fa[u]:u);
        rotate(u);
    }
}
void access(int u){
    for(int v=0;u;v=u,u=fa[u]){
        splay(u);
        ch[u][1]=v;
        pushup(u);
    }
}
void makeroot(int u){
    access(u);
    splay(u);
    reverse(u);
}
void link(int a,int b){
    makeroot(a);
    fa[a]=b;
}
void cut(int a,int b){
    makeroot(a);
    access(b);
    splay(b);
    ch[b][0]=0;
    fa[a]=0;
    pushup(b);
}
int main(){
    n=rd();
    for(int i=1;i<=n;i++){
        num[i]=rd();
        siz[i]=1;
    }
    for(int i=1;i<=n;i++){
        if(i+num[i]<=n)fa[i]=i+num[i];
        else fa[i]=n+1;
    }
    q=rd();
    for(int i=1;i<=q;i++){
        int opt=rd(),x=rd()+1;
        if(opt==1){
            makeroot(n+1);
            access(x);
            splay(x);
            printf("%d\n",siz[ch[x][0]]);
        }else{
            int y=rd();
            if(x+num[x]<=n)cut(x,x+num[x]);
            else cut(x,n+1);
            num[x]=y;
            if(x+num[x]<=n)link(x,x+num[x]);
            else link(x,n+1);
        }
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值