https://vjudge.net/problem/HYSBZ-2002
思路:用
c
n
t
[
i
]
cnt[i]
cnt[i]记录从位置
i
i
i出发被弹飞的次数,那么逆序处理的话可以
O
(
n
)
O(n)
O(n)计算出
c
n
t
cnt
cnt数组,但是每次修改
a
[
i
]
a[i]
a[i]的弹力系数时,都要重新需改
c
n
t
[
1
…
i
]
cnt[1…i]
cnt[1…i],这样复杂度是
O
(
n
∗
m
)
O(n*m)
O(n∗m)的,肯定过不了。我们考虑降低一下对
c
n
t
cnt
cnt的要求。即对原序列进行分块,设
i
i
i属于第
j
j
j块,那么用
c
n
t
[
i
]
cnt[i]
cnt[i]记录从
i
i
i出发移动到
>
j
>j
>j的块所需要的次数,
w
z
[
i
]
wz[i]
wz[i]记录最终移动到的位置。这两个数组也可以在
O
(
n
)
O(n)
O(n)内计算出来,但是每次修改只需要修改
i
i
i所在块的
c
n
t
、
w
z
cnt、wz
cnt、wz(实际上右边界可以缩小到
i
i
i),单次复杂度仅仅只有
O
(
n
)
O(\sqrt n)
O(n),所以总体复杂度是
O
(
m
∗
n
)
O(m*\sqrt n)
O(m∗n)。
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int maxn=2e5+5;
int n;
int a[maxn],pos[maxn],cnt[maxn],wz[maxn];
int l[2000],r[2000];
//cnt[i]记录从当前位置跳到该块之后所需要的最少步数
//wz[i]记录跳到的位置
inline void update(int p)
{
int posr=r[pos[p]]; //p所在块的右区间端点
cnt[p]=1,wz[p]=p+a[p];
if(wz[p]<=posr)
{
cnt[p]+=cnt[wz[p]];
wz[p]=wz[wz[p]];
}
}
inline int query(int p)
{
int ct=0;
while(p<=n)
{
ct+=cnt[p];
p=wz[p];
}
return ct;
}
int main()
{
scanf("%d",&n);
int dis=sqrt(n);
int num=ceil(n*1.0/dis);
for(int i=1;i<=num;i++)
l[i]=(i-1)*dis+1,r[i]=i*dis;
r[num]=n;
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
pos[i]=(i-1)/dis+1;
}
for(int i=n;i>=1;i--)
update(i);
int m,op,u,v;
scanf("%d",&m);
while(m--)
{
scanf("%d%d",&op,&u);
++u;
if(op==1)
printf("%d\n",query(u));
else
{
scanf("%d",&v);
if(a[u]==v)
continue;
a[u]=v;
int posl=l[pos[u]];
for(int i=u;i>=posl;i--)
update(i);
}
}
return 0;
}