【BZOJ2002】[HNOI2010] 弹飞绵羊(大力分块)

点此看题面

大致题意: 有\(n\)个弹力装置,当到达第\(i\)个装置时,会被弹到第\(i+k_i\)个装置,若不存在第\(i+k_i\)个装置,就会被弹飞。有两种操作,一种操作是将\(k_x\)改为\(y\),另一种操作是询问从\(x\)出发被弹几次后会被弹飞。

考虑分块

这题可以用分块来做。

我们可以将弹力装置进行分块,对于每一块的弹力装置,可以先预处理出每个弹力元素弹出这个块之后到达的位置\(nxt_x\)以及需要的步骤\(val_x\)

对于询问操作,从询问的\(x\)出发,每一次将\(ans\)加上\(val_x\),然后将\(x\)更新为\(nxt_x\),直至\(x\)大于\(n\),由于每个块最多被经过一次,因此时间复杂度是\(O(\sqrt n)\)的。

而对于修改操作,只要修改一个块内的\(nxt\)和\(val\)即可,不难发现修改也是\(O(\sqrt n)\)的。

因此总复杂度\(O(m\sqrt n)\),对于\(n≤200000,m≤100000\)的数据跑起来毫无压力。

代码

#include<bits/stdc++.h>
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)<(y)?(x):(y))
#define LL long long
#define swap(x,y) (x^=y,y^=x,x^=y)
#define tc() (A==B&&(B=(A=ff)+fread(ff,1,100000,stdin),A==B)?EOF:*A++)
#define pc(ch) (pp_<100000?pp[pp_++]=(ch):(fwrite(pp,1,100000,stdout),pp[(pp_=0)++]=(ch)))
#define N 200000
#define SIZE 450
#define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
int pp_=0;char ff[100000],*A=ff,*B=ff,pp[100000];
using namespace std;
int n,Q,blo,a[N+5],pos[N+5],nxt[N+5],val[N+5];
inline void read(int &x)
{
x=0;static char ch;
while(!isdigit(ch=tc()));
while(x=(x<<3)+(x<<1)+ch-48,isdigit(ch=tc()));
}
inline void write(int x)
{
if(x>9) write(x/10);
pc(x%10+'0');
}
int main()
{
register int i,op,x,y,limit,ans;
for(read(n),blo=sqrt(n),i=1;i<=n;++i) read(a[i]),pos[i]=(i-1)/blo+1;
for(i=n;i;--i)//先预处理出nxt[]和val[]数组
{
if(pos[i]^pos[nxt[i]=i+a[i]]) val[i]=1;//如果直接弹出这个块,那么所需步骤为1
else val[i]=val[nxt[i]]+1,nxt[i]=nxt[nxt[i]];//否则,步骤数就是它所到达的下一个节点弹出这个块的步骤,且它弹出这个块后到达的元素就是它所到达的下一个节点弹出这个块后到达的元素
}
for(read(Q);Q;--Q)
{
read(op),read(x),++x;//因为数据是0~n-1编号的,而我习惯于从1~n编号,因此将读入的编号x加1
if(op^2)//对于询问操作
{
for(ans=0;x<=n;x=nxt[x]) ans+=val[x];//将ans加上x弹出当前块所需的步骤,并将x更新为它弹出当前块后到达的节点
write(ans),pc('\n');//输出ans
}
else
{
for(read(y),a[x]=y,limit=(pos[x]-1)*blo+1,i=pos[x]*blo;i>=limit;--i)//更新这个块中的信息
{
if(pos[i]^pos[nxt[i]=i+a[i]]) val[i]=1;//和初始化的代码差不多
else val[i]=val[nxt[i]]+1,nxt[i]=nxt[nxt[i]];
}
}
}
return fwrite(pp,1,pp_,stdout),0;
}
上一篇:[洛谷P3203][HNOI2010]弹飞绵羊


下一篇:Scrum不是万能药,要在时机成熟时推行