有N个点,每个点有一个系数a[i],你处于位置i可以走到i+a[i],若i+a[i]>n则你走出了地图。现M个操作有两种:1、把a[j]修改为k。2、询问你位于点j时,需要走多少部走出地图。n<=2*10^5,m<=10^5。
我们可以转化为如下问题:1、将x的父亲设为y。2、询问x的深度。
那就变成了LCT裸题,为了支持询问操作只需要维护size即可。
#include<cstdio>
#include<algorithm>
#include<stack>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
const int maxn=200000+10;
stack<int> sta;
int father[maxn],size[maxn],pp[maxn],tree[maxn][2],a[maxn];
bool bz[maxn];
int i,j,k,l,t,n,m;
void update(int x){
size[x]=size[tree[x][0]]+size[tree[x][1]]+1;
}
int pd(int x){
return tree[father[x]][1]==x;
}
void rotate(int x){
int y=father[x],z=pd(x);
tree[y][z]=tree[x][1-z];
if (tree[x][1-z]) father[tree[x][1-z]]=y;
father[x]=father[y];
if (father[y]) tree[father[y]][pd(y)]=x;
tree[x][1-z]=y;
father[y]=x;
update(y);
update(x);
if (pp[y]) pp[x]=pp[y],pp[y]=0;
}
void clear(int x){
if (bz[x]){
bz[x]=0;
if (tree[x][0]) bz[tree[x][0]]^=1;
if (tree[x][1]) bz[tree[x][1]]^=1;
swap(tree[x][0],tree[x][1]);
}
}
void remove(int x,int y){
while (x!=y){
sta.push(x);
x=father[x];
}
while (!sta.empty()){
clear(sta.top());
sta.pop();
}
}
void splay(int x,int y){
remove(x,y);
while (father[x]!=y){
if (father[father[x]]!=y)
if (pd(x)==pd(father[x])) rotate(father[x]);else rotate(x);
rotate(x);
}
}
void access(int x){
int y;
splay(x,0);
father[tree[x][1]]=0;
if (tree[x][1]) pp[tree[x][1]]=x;
tree[x][1]=0;
update(x);
while (pp[x]){
y=pp[x];
splay(y,0);
father[tree[y][1]]=0;
if (tree[y][1]) pp[tree[y][1]]=y;
tree[y][1]=x;
father[x]=y;
pp[x]=0;
update(y);
splay(x,0);
}
}
void makeroot(int x){
access(x);
splay(x,0);
bz[x]^=1;
}
void cut(int x,int y){
access(x);
splay(y,0);
pp[y]=0;
}
void link(int x,int y){
makeroot(x);
splay(y,0);
pp[y]=x;
makeroot(n+1);
}
int main(){
scanf("%d",&n);
fo(i,1,n){
scanf("%d",&a[i]);
j=(i+a[i]>n)?n+1:i+a[i];
link(j,i);
}
scanf("%d",&m);
while (m--){
scanf("%d%d",&t,&j);
j++;
if (t==1){
access(j);
splay(j,0);
printf("%d\n",size[tree[j][0]]);
}
else{
scanf("%d",&k);
l=(j+a[j]>n)?n+1:j+a[j];
cut(l,j);
a[j]=k;
l=(j+a[j]>n)?n+1:j+a[j];
link(l,j);
}
}
}
不过我们有更好的方法。
我们分块后,对每一个元素维护两个东西:b[i]表示i一直跳跳出自己所在块后跳到了哪个点,d[i]表示i跳出自己所在块的所需步数。有了这两个数组就可以进行询问,而修改操作暴力重建整个块就好了。
#include<cstdio>
#include<algorithm>
#include<cmath>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
const int maxn=200000+10;
int a[maxn],b[maxn],d[maxn];
int i,j,k,l,t,n,m,ans,c;
int main(){
scanf("%d",&n);
c=floor(sqrt(n))+1;
fo(i,1,n) scanf("%d",&a[i]);
fd(i,n,1){
k=min(((i-1)/c+1)*c,n);
if (i+a[i]>k) d[i]=1,b[i]=i+a[i];
else d[i]=d[i+a[i]]+1,b[i]=b[i+a[i]];
}
scanf("%d",&m);
while (m--){
scanf("%d",&t);
if (t==1){
scanf("%d",&j);
j++;
ans=0;
while (j<=n){
ans+=d[j];
j=b[j];
}
printf("%d\n",ans);
}
else{
scanf("%d%d",&j,&k);
j++;
a[j]=k;
k=min(((j-1)/c+1)*c,n);
fd(i,k,(j-1)/c*c+1){
if (i+a[i]>k) d[i]=1,b[i]=i+a[i];
else d[i]=d[i+a[i]]+1,b[i]=b[i+a[i]];
}
}
}
}