洛谷-P3203 弹飞绵羊 分块

题目

题目链接

题意

据说这道题要用一道叫做LCT的数据结构,然而我不会。。。

一排有n个弹簧装置,从第 i i 个可一往后跳k[i]步。

  • 修改:修改某个位置弹簧的弹性。
  • 查询:询问从某个位置出发弹多少次就弹飞了。

题解

分块首先都要先从暴力开始想起:
我们记录从任意一个点出发,下一次能跳到哪里,这样的话我们每次询问,只需要一只沿着这条链往下走即可,显然最坏的时间复杂度是 O(n2) O ( n 2 ) ,修改的时间复杂度为 O(1) O ( 1 )

优化:
让我们使用分块来进行优化,我们把一条直线上的n个弹簧进行分块,并对于每个弹簧记录两个属性: sum[i] s u m [ i ] nxtb[i] n x t b [ i ]

其中 sum[i] s u m [ i ] 表示的含义是从 i i 弹簧出发在本块内的经过的节点的个数,nxtb[i]表示的是从 i i <script type="math/tex" id="MathJax-Element-479">i</script>弹簧出发,弹到下一块中的第一个弹簧的编号,弹飞设置为-1。

这样的话,询问的时候我们只需要下面一段代码就可以了。

int now = x;
while(x != -1){
    ans += sum[now];
    now = nxtb[now];
}

预处理的话,我们要倒着往前计算,因为前面的sum用到了后面的sum。
预处理和修改的话详情请见代码。

代码

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
using namespace std;
const int maxn = 200007;
#define pr(x) cout<<#x<<":"<<x<<endl
int Base = 450;
int nxtb[maxn];
int val[maxn];
int sum[maxn];
int n,m,op,x,y;
inline void read(int &x){
    scanf(" %d",&x);
}
int main(){
    read(n);
    Base = (int) sqrt(n+0.5);
    for(int i = 1;i <= n;++i){
        read(val[i]);
    }

    for(int i = n;i >= 1;--i){
        int j = val[i] + i;
        int bl = i / Base;
        int br = j / Base;
        if(j > n){
            nxtb[i] = -1;
            sum[i] = 1;
        }
        else if(br == bl){
            //属于同一个块
            nxtb[i] = nxtb[j];
            sum[i] = sum[j]+1;
        }
        else{
            //不属于同一个块
            nxtb[i] = j;
            sum[i] = 1;
        }
    }

    read(m);
    while(m--){
        read(op);
        if(op == 1){
            //询问
            read(x);
            x++;
            int ans = 0;
            int now = x;
            while(now != -1){
                ans += sum[now];
                now = nxtb[now];
            }
            printf("%d\n",ans);
        }
        else{
            //修改
            read(x);read(y);
            x++;
            val[x] = y;
            int j = x + y;
            int bl = x / Base;
            int br = j / Base;
            if(j > n){
                sum[x] = 1;
                nxtb[x] = -1;
            }
            else if(bl == br){
                //属于用一个块
                sum[x] = sum[j] + 1;
                nxtb[x] = nxtb[j];
            }
            else{
                //不属于同一个块
                sum[x] = 1;
                nxtb[x] = j;
            }
            for(int i = x-1;i >= max(1,bl*Base);--i){
                //更新与x属于同一块的可能被x影响的弹簧。
                j = i + val[i];
                br = j / Base;
                if(br == bl){
                    sum[i] = sum[j] + 1;
                    nxtb[i] = nxtb[j];
                }
            }
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值