[HNOI2010]弹飞绵羊
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
4
1 2 1 1
3
1 1
2 1 1
1 1
1 2 1 1
3
1 1
2 1 1
1 1
Sample Output
2
3
先建树,如果i+k大于n,就和n+1,建边表示弹出,否则和i+k建边
建树用LCT的link操作
修改就cut操作删掉旧边,加上新边
查询时,把n+1变为树根,把x到n+1都变为实根
查询路径上节点数-1输出
splay维护size,因为总是只有一颗完整的树,所以不需要判断cut和link操作的可行性
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #include<cmath> 6 using namespace std; 7 int size[200005],ch[200005][2],rev[200005],pre[200005],isrt[200005],n,m,k[200005]; 8 void pushup(int o) 9 { 10 if (!o) return; 11 size[o]=size[ch[o][0]]+size[ch[o][1]]+1; 12 } 13 void pushdown(int o) 14 { 15 if (!o) return; 16 if (rev[o]) 17 { 18 int ls=ch[o][0],rs=ch[o][1]; 19 rev[ls]^=1; 20 swap(ch[ls][0],ch[ls][1]); 21 rev[rs]^=1; 22 swap(ch[rs][0],ch[rs][1]); 23 rev[o]=0; 24 } 25 } 26 void push(int o) 27 { 28 if (isrt[o]==0) push(pre[o]); 29 pushdown(o); 30 } 31 void rotate(int o,bool kind) 32 { 33 int p=pre[o]; 34 ch[p][!kind]=ch[o][kind];pre[ch[o][kind]]=p; 35 if (isrt[p]) isrt[p]=0,isrt[o]=1; 36 else ch[pre[p]][ch[pre[p]][1]==p]=o; 37 pre[o]=pre[p]; 38 ch[o][kind]=p;pre[p]=o; 39 pushup(p);pushup(o); 40 } 41 void splay(int o) 42 { 43 push(o); 44 while (isrt[o]==0) 45 { 46 if (isrt[pre[o]]) 47 rotate(o,ch[pre[o]][0]==o); 48 else 49 { 50 int p=pre[o],kind=ch[pre[p]][0]==p; 51 if (ch[p][kind]==o) 52 rotate(o,!kind),rotate(o,kind); 53 else rotate(p,kind),rotate(o,kind); 54 } 55 } 56 } 57 void access(int o) 58 { 59 int y=0; 60 while (o) 61 { 62 splay(o); 63 isrt[ch[o][1]]=1; 64 isrt[ch[o][1]=y]=0; 65 pushup(o); 66 y=o;o=pre[o]; 67 } 68 } 69 void makeroot(int o) 70 { 71 access(o); 72 splay(o); 73 rev[o]^=1; 74 swap(ch[o][0],ch[o][1]); 75 } 76 void link(int x,int y) 77 { 78 makeroot(x); 79 pre[x]=y; 80 } 81 void cut(int x,int y) 82 { 83 makeroot(x); 84 access(y);splay(y); 85 ch[y][0]=0;pre[x]=0; 86 isrt[x]=1; 87 pushup(y); 88 } 89 int main() 90 {int i,c,x,y,st,ed; 91 cin>>n; 92 for (i=1;i<=n+1;i++) 93 isrt[i]=1,size[i]=1; 94 for (i=1;i<=n;i++) 95 { 96 scanf("%d",&k[i]); 97 if (i+k[i]>n) link(i,n+1); 98 else link(i,i+k[i]); 99 } 100 cin>>m; 101 for (i=1;i<=m;i++) 102 { 103 scanf("%d",&c); 104 if (c==1) 105 { 106 scanf("%d",&x);x++; 107 makeroot(n+1); 108 access(x); 109 splay(x); 110 printf("%d\n",size[x]-1); 111 } 112 else 113 { 114 scanf("%d%d",&x,&y);x++; 115 if (k[x]+x>n) st=n+1; 116 else st=k[x]+x; 117 if (y+x>n) ed=n+1; 118 else ed=y+x; 119 k[x]=y; 120 if (st!=ed) 121 { 122 cut(x,st); 123 link(x,ed); 124 } 125 } 126 } 127 }
还有分块的做法
把n个点分成$sqrt n$块
预处理出f[i],s[i]
f[i]表示i会跳到下一块的地方
s[i]表示i跳到f[i]所需要的步数
查询时直接往后跳,最多跳$sqrt n$块
修改时只修改那一块$sqrt n$个元素
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #include<cmath> 6 using namespace std; 7 int n,k[300001],lim,s[300001],f[300001],m; 8 int main() 9 {int i,j,opt,x,now,cnt,y; 10 cin>>n; 11 for (i=1;i<=n;i++) 12 { 13 scanf("%d",&k[i]); 14 } 15 lim=sqrt(n); 16 for (i=1;i<=n;i+=lim) 17 { 18 for (j=min(n,i+lim-1);j>=i;j--) 19 { 20 if (k[j]+j>min(n,i+lim-1)) s[j]=1,f[j]=k[j]+j; 21 else s[j]=s[j+k[j]]+1,f[j]=f[k[j]+j]; 22 } 23 } 24 cin>>m; 25 for (i=1;i<=m;i++) 26 { 27 scanf("%d%d",&opt,&x);x++; 28 if (opt==1) 29 { 30 cnt=0; 31 while (x<=n) 32 { 33 cnt+=s[x]; 34 x=f[x]; 35 } 36 printf("%d\n",cnt); 37 } 38 else 39 { 40 scanf("%d",&y); 41 k[x]=y; 42 if (x%lim==0) now=x-lim+1; 43 else now=x-x%lim+1; 44 for (j=min(n,now+lim-1);j>=now;j--) 45 { 46 if (k[j]+j>min(n,now+lim-1)) s[j]=1,f[j]=k[j]+j; 47 else s[j]=s[j+k[j]]+1,f[j]=f[k[j]+j]; 48 } 49 } 50 } 51 }