【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√
)。
之所以玄学,是因为分的块的大小会决定你的时间复杂度和空间复杂度,弄不好就会爆MLE和TLE………
确定了分块就可以定状态了,
我们设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();
}
}
可以看到有两种建块方法,具体复杂度请自己参考