【HNOI2010】弹飞绵羊 分块/LCT

【HNOI2010】弹飞绵羊

问题描述

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

输入格式

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

输出格式

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

样例输入

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

样例输出

2
3


当时我并不会LCT。但是这道题可以用分块做。

按照分块的思想,我们想知道某个位置跳出当前块需要的步数,这样就可以大块大块地跳,最多跳 n 次。为了配套,同时也要处理出跳出当前块后走到的第一个位置。这样就可以让查询操作的时间复杂度控制在 O(n) 级别。

考虑如何处理出这两个数组。为了表示方便,记从i位置跳出它所在的块需要的步数为 step[i] ,跳出它所在块走到的第一个位置为 nex[i]

首先根据题目可知,一个位置的弹力系数改变后,答案会受到影响的部分只在它的左边。所以应该从右往左讨论。一开始每个位置跳到的下一个位置是 i+ki 。如果 i+ki 已经超出了当前的区间,那么 nex[i] 就是 i+ki , step[i] 就是1。否则 nex[i]=nex[i+ki] , step[i]=step[i+ki]+1

修改操作的处理方法是相同的。那么这道题就解决了,时间复杂度 O(nn)


#include<stdio.h>
#include<cmath>
#define MAXN 200005

int N,M,S,be[MAXN],A[MAXN],L[MAXN],R[MAXN],step[MAXN],nex[MAXN];

void Modify(int x)
{
    int i,tmp;
    for(i=x;i>=L[be[x]];i--)
    {
        tmp=A[i]+i;
        if(tmp>N)tmp=N+1;
        if(be[tmp]==be[i])step[i]=step[tmp]+1,nex[i]=nex[tmp];
        else step[i]=1,nex[i]=tmp;
    }
}

int GetAns(int x)
{
    int Ans=0;
    while(x<=N)
    {
        Ans+=step[x];
        x=nex[x];
    }
    return Ans;
}

int main()
{
    int i,j,op,x,y,tmp;

    scanf("%d",&N);

    for(i=1;i<=N;i++)scanf("%d",&A[i]);
    S=sqrt(N);
    for(i=j=1,L[1]=1;i<=N;i++)
    {
        be[i]=j;
        if(i%S==0)R[j]=i,L[++j]=i+1;
    }
    R[be[N]]=N;

    nex[N+1]=N+1;
    for(j=1;j<=be[N];j++)
    for(i=R[j];i>=L[j];i--)
    {
        tmp=A[i]+i;
        if(tmp>N)tmp=N+1;
        if(be[tmp]==be[i])step[i]=step[tmp]+1,nex[i]=nex[tmp];
        else step[i]=1,nex[i]=tmp;
    }

    scanf("%d",&M);
    for(i=1;i<=M;i++)
    {
        scanf("%d",&op);
        if(op==1)
        {
            scanf("%d",&x);x++;
            printf("%d\n",GetAns(x));
        }
        else
        {
            scanf("%d%d",&x,&y);x++;
            A[x]=y;Modify(x);
        }
    }
}

现在会LCT了,说说LCT的做法。

添加一个虚拟节点 n+1 表示坐标超过 n 。如果把i i+k[i] 连一条边,那么得到的图一定是一棵树。对于 i=1 的操作,输出根节点到它的边数即可,这个显然就是 SetRoot(n+1)+Access(x) ,求出 x 所在Splay的size1即可(边数=点数-1)。

对于修改操作,就是先 Cut ,后 Link


#include<stdio.h>
#include<algorithm>
#define MAXN 200005
using namespace std;

int N,M,A[MAXN];

int ls[MAXN],rs[MAXN],fa[MAXN],Size[MAXN],rev[MAXN];

bool isrt(int x){return ls[fa[x]]!=x&&rs[fa[x]]!=x;}

void Putdown(int p)
{
    if(rev[p]==0)return;
    rev[p]=0;rev[ls[p]]^=1;rev[rs[p]]^=1;
    swap(ls[p],rs[p]);
}

void Update(int p){Size[p]=Size[ls[p]]+Size[rs[p]]+1;}

void Zig(int x)
{
    int y=fa[x],z=fa[y];
    if(!isrt(y))
    {
        if(ls[z]==y)ls[z]=x;
        else rs[z]=x;
    }
    fa[x]=z;fa[y]=x;fa[rs[x]]=y;
    ls[y]=rs[x];rs[x]=y;
    Update(y);Update(x);
}

void Zag(int x)
{
    int y=fa[x],z=fa[y];
    if(!isrt(y))
    {
        if(ls[z]==y)ls[z]=x;
        else rs[z]=x;
    }
    fa[x]=z;fa[y]=x;fa[ls[x]]=y;
    rs[y]=ls[x];ls[x]=y;
    Update(y);Update(x);
}

int s[MAXN],Top;
void Splay(int x)
{
    int i,y,z;

    s[++Top]=x;
    for(i=x;!isrt(i);i=fa[i])s[++Top]=fa[i];
    while(Top)Putdown(s[Top--]);

    while(!isrt(x))
    {
        y=fa[x];z=fa[y];
        if(isrt(y))
        {
            if(ls[y]==x)Zig(x);
            else Zag(x);
        }
        else 
        {
            if(ls[z]==y)
            {
                if(ls[y]==x)Zig(y),Zig(x);
                else Zag(x),Zig(x);
            }
            else
            {
                if(rs[y]==x)Zag(y),Zag(x);
                else Zig(x),Zag(x);
            }
        }
    }
}

void Access(int x)
{
    int t=0;
    while(x)
    {
        Splay(x);
        rs[x]=t;Update(x);
        t=x;x=fa[x];
    }
}

void Setrt(int x)
{
    Access(x);Splay(x);
    rev[x]^=1;
}

void Link(int x,int y)
{
    Setrt(x);fa[x]=y;
}

void Cut(int x,int y)
{
    Setrt(x);
    Access(y);Splay(y);
    ls[y]=fa[x]=0;
    Update(y);
}

int GetSum(int x)
{
    Setrt(N+1);
    Access(x);
    Splay(x);
    return Size[x]-1;
}

int main()
{
    int i,x,y,op,k;

    scanf("%d",&N);
    for(i=1;i<=N;i++)
    {
        scanf("%d",&x);
        A[i]=x;y=i+x;
        if(y>N+1)y=N+1;
        Link(i,y);
    }

    for(i=1;i<=N+1;i++)Size[i]=1;

    scanf("%d",&M);
    while(M--)
    {
        scanf("%d",&op);
        if(op==1)
        {
            scanf("%d",&x);x++;
            printf("%d\n",GetSum(x));
        }
        else
        {
            scanf("%d%d",&x,&k);
            x++;
            y=A[x]+x;
            if(y>N+1)y=N+1;
            Cut(x,y);
            y=x+k;
            if(y>N+1)y=N+1;
            Link(x,y);
            A[x]=k;
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值