Description
某天,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的情况,你都要输出一个需要的步数,占一行。
Sample Input
41 2 1 1 31 12 1 11 1
Sample Output
23
HINT
Source
直接跳的话复杂度是O(nm),显然是无法接受的。显然我们无法优化询问这个m,那么如何从n入手优化呢?
如果跳的次数能有效减少,那么对于时间的优化是巨大的,所以我们考虑预处理一下从某个点跳到某个点需要几步
最直接的思考,倒着递推O(n),预处理每个点跳到终点要几步?对于修改较少的输入时可以接受的,可是一旦修改的次数增多,复杂度还是O(nm),
那么取折衷的方案是,我们考虑把n个数字分成若干段,每一段的大小是sqrt(n),然后预处理每一个点跳到另外的位置(该位置属于另一个区块,也有可能是不相邻的区块,因为一步可能跳很远,比一块的大小还远),以及从这个点到另一个块的那个位置需要的步数。这要查询的时候最多只要sqrt(n)的跳跃就能出结果。对于修改,我们只需要修改要修改的那个点属于的块中每个点跳到另外块的位置以及需要的步数即可,因为每个块只有sqrt(n)个点,所以总体复杂度为O(m*sqrt(n))。
#include <cstdio>
#include <stdlib.h>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cmath>
#include <cassert>
using namespace std;
const int maxn = 222222;
//const int maxn = 22;
//to[i]:从i跳到另外的块的位置 group[i]:i点是在哪一块 step[i]:从i跳到另外的块需要的步数
int to[maxn], group[maxn], step[maxn], k[maxn], n, size;
void solve(int l, int r) {
for (int i = r; i >= l; i --) {
if (i + k[i] >= n) {
to[i] = -1;
step[i] = 1;
} else {
if (group[i] == group[i+k[i]]) {
to[i] = to[i+k[i]];
step[i] = step[i+k[i]] + 1;
} else {
to[i] = i + k[i];
step[i] = 1;
}
}
}
}
int work(int u) {
int ret = 0;
while (u != -1) {
ret += step[u];
u = to[u];
}
return ret;
}
int main() {
scanf("%d",&n); size = (int)sqrt(n);
for (int i = 0; i < n; i ++) scanf("%d",&k[i]);
for (int i = 0; i < n; i ++) {
group[i] = i / size;
}
solve(0, n-1);
int m; scanf("%d",&m);
while (m--) {
int op, pos; scanf("%d%d",&op,&pos);
if (op == 1) {
printf("%d\n",work(pos));
} else {
int x; scanf("%d",&x);
k[pos] = x;
int l = pos / size * size, r = l + size - 1;
solve(l, r);
}
}
return 0;
}