【HNOI2010】弹飞绵羊

【HNOI2010】弹飞绵羊

时间限制 : 10000 MS 空间限制 : 265536 KB

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

INPUT

第一行包含一个整数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

OUTPUT

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

样例输入

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

样例输出

2
3

200000个点的数轴上查询和操作,在这种大数据下显然需要O(n log2n )左右 的算法,乍一看可能会想到线段树,然而实际操作起来是不行的.

事实上,这是一道动态树(LCT) 的模板题,然而我并没有学过…….
那么,我们就可以想到一个比较通用而玄学的结构了——分块,他可以在分块解决的基础上只在块的数量级上进行运算,一般为O(n n )。

之所以玄学,是因为分的块的大小会决定你的时间复杂度空间复杂度,弄不好就会爆MLETLE………

确定了分块就可以定状态了,
我们设Cnt[i]表示i号节点弹到下一块需要的次数,显然有——若i号节点到下一个节点(通过弹跳)是属于同一块的,那么Cnt[i]=Cnt[i+s[i]]+1。否则,Cnt[i]=1
同理,设To[i]表示i号节点到下一块弹跳到的第一个节点代号,也能通过是否同一块推算出是否相同(具体见代码)。

递推关系出来其实代码就很简单了,感觉有点像一些思维DP题…….
废话了半天,代码如下:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll maxn=200005;
ll belong[maxn],Left[maxn],n,m,cnt[maxn],to[maxn],t[maxn];
void ask() {
    ll ee;
    cin>>ee;
    ll ans=0;
    while(1) {
        ans+=cnt[ee];
        if(!to[ee]) break;
        ee=to[ee];
    }
    cout<<ans<<endl;
}
void change() {
    ll x,v,i,j,k;
    cin>>x>>v;
    t[x]=v;
    ll le=Left[belong[x]];
    for(i=x; i>=le; i--) {

        if(i+t[i]>=n) {
            cnt[i]=1;
            to[i]=0;
        }
        else if(belong[i]==belong[i+t[i]]) {
            cnt[i]=cnt[i+t[i]]+1;
            to[i]=to[i+t[i]];
        }

        else {
            cnt[i]=1;
            //if(i+t[i]>=n) to[i]=0;
            to[i]=i+t[i];
        }
    }
}
int main() {
    cin>>n;
    ll i,j,k;
    for(i=0; i<n; i++)
        cin>>t[i];
    ll tcnt=0;
    ll pp=0;
    ll sq=sqrt(n);
    for(i=0; i<n; i++) {
        if(pp>=sq) pp=0;
        if(pp==0) Left[++tcnt]=i;
        belong[i]=tcnt;
        pp++;
    } /* ll block=sqrt(n);ll tcnt=n/block;  
    if(n%block) tcnt++;  

    for(ll i=1;i<=tcnt;i++)  
    {  
          Left[i]=block*(i-1)+1;//r[i]=block*i;  
    }  
    //r[cnt]=n;  
    for(ll i=0;i<n;i++) belong[i]=i/block+1;*/ 
    for(i=n-1; i>=0; i--) {
        if(i+t[i]>=n) {
            cnt[i]=1;
            to[i]=0;
        }
        else if(belong[i]==belong[i+t[i]]) {
            cnt[i]=cnt[i+t[i]]+1;
            to[i]=to[i+t[i]];
        } else {
            cnt[i]=1;
            //if(i+t[i]>=n) to[i]=0;
             to[i]=i+t[i];
        }
    }
    cin>>m;
    for(i=1; i<=m; i++) {
        ll tt;
        cin>>tt;
        if(tt==1) ask();
        if(tt==2) change();
    }

}

可以看到有两种建块方法,具体复杂度请自己参考

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值