LCT复习之 bzoj 2002: [Hnoi2010]Bounce 弹飞绵羊

题意

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

题解

以前用分块做过一次,当时还是自己想出来分块的做法呢!
但是,今天,我学会了LCT
于是我把这题又做了一次
做法也很简单
如果我们把终点看成一个节点,假设是n+1吧
然后每个点和他能到达的点相连
不难发现,这是一颗以n+1为根的有根树
答案就是这个点到根的路径有多少个节点
LCT维护即可
注意查询答案的时候,人为地把n+1变回根就可以了
稍微地比分块快一点点啊

CODE:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=200005;
int n,m;
int a[N];
struct qq
{
    int c,fa,son[2];
    bool rev;
}tr[N];
bool is_root (int x)
{
    int fa=tr[x].fa;
    if (tr[fa].son[0]==x) return false;
    if (tr[fa].son[1]==x) return false;
    return true;
}
void push_down (int x)
{
    if (!tr[x].rev) return ;
    int s1=tr[x].son[0],s2=tr[x].son[1];
    swap(tr[x].son[0],tr[x].son[1]);
    tr[s1].rev^=1;tr[s2].rev^=1;tr[x].rev^=1;
}
void pre (int x)
{
    if (!is_root(x)) pre(tr[x].fa);
    push_down(x);
}
void update (int now)
{
    int s1=tr[now].son[0],s2=tr[now].son[1];
    tr[now].c=1+tr[s1].c+tr[s2].c;
}
void rotate (int x)
{
    int y=tr[x].fa,z=tr[y].fa,r,R;
    int w=(tr[y].son[0]==x);

    r=tr[x].son[w];R=y;
    tr[R].son[1-w]=r;
    if (r!=0) tr[r].fa=R;

    r=x;R=z;
    if (!is_root(y))
    {
        if (tr[R].son[0]==y) tr[R].son[0]=r;
        else tr[R].son[1]=r;
    }
    tr[r].fa=R;

    r=y;R=x;
    tr[R].son[w]=r;
    tr[r].fa=R;

    update(y);update(x);
}
void splay (int x)//使得x成为所在splay的根 
{
    pre(x);
    while (!is_root(x))
    {
        int y=tr[x].fa,z=tr[y].fa;
        if (!is_root(y))
        {
            if ((tr[z].son[0]==y)==(tr[y].son[0]==x)) rotate(y);
            else rotate(x);
        }
        rotate(x);
    }
}
void access (int x)//使得x成为偏爱路径 
{
    int last=0;
    while (x!=0)
    {
        splay(x);
        tr[x].son[1]=last;
        update(x);
        last=x;
        x=tr[x].fa;
    }
}
void make_root (int x)//使得x成为所在树的跟 
{
    access(x);splay(x);tr[x].rev^=1;
}
void link (int x,int y)//连接这两个点 注意,原来是x跳到y的,因此,y的深度比x小 
{
    if (y>n) y=n+1;
    make_root(x);tr[x].fa=y;
}
void split (int x,int y)//分离这两个点的路径,y当根 
{
    make_root(x);
    access(y);
    splay(y);
}
void cut (int x,int y) 
{
    if (y>n) y=n+1;
    split(x,y);
    tr[y].son[0]=tr[x].fa=0;
    update(y);
}
int solve (int x)
{
    make_root(n+1);
    access(x);splay(x);
    return tr[x].c;
}
int main()
{
    scanf("%d",&n);
    tr[n+1].c=1;tr[n+1].rev=false;
    for (int u=1;u<=n;u++)  
    {
        scanf("%d",&a[u]);
        tr[u].c=1;tr[u].rev=false;
    }
    for (int u=n;u>=1;u--)
        link(u,u+a[u]);
    scanf("%d",&m);
    for (int u=1;u<=m;u++)
    {
        int op;
        scanf("%d",&op);
        if (op==1)
        {
            int x;
            scanf("%d",&x);x++;
            printf("%d\n",solve(x)-1);
        }
        else
        {
            int x,y;
            scanf("%d%d",&x,&y);x++;
            cut(x,x+a[x]);
            a[x]=y;
            link(x,x+a[x]);
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值