- 有一棵 \(n\) 个点的以 \(1\) 为根的有根树,其中 \(i\) 号点的初始父节点为 \(a_i\)(\(1\le a_i < i\))。
- \(q\) 次操作,分为两种:将 \([l,r]\) 中所有 \(a_i\) 修改为 \(\max\{a_i-x,1\}\);询问 \(\operatorname{LCA}(x,y)\)。
- \(2\le n,q\le4\times10^5\),\(1\le x\le4\times10^5\),强制在线
分块求 \(\operatorname{LCA}\)
我们首先要解决的问题是如何利用分块求出 \(\operatorname{LCA}\)。
记 \(a_i\) 为 \(i\) 的父节点,\(f_i\) 为 \(i\) 首个不在当前块的祖先。然后进行一系列讨论:
- 如果当前 \(x,y\) 不同块:让后面那个块的点跳 \(f\)。
- 如果当前 \(x,y\) 同块,但 \(f_x\not=f_y\):如果 \(x,y\) 的 \(\operatorname{LCA}\) 在这个块内则首个不在当前块的祖先必然相同,也就是说 \(x,y\) 的 \(\operatorname{LCA}\) 不在当前块中,让 \(x,y\) 同时跳 \(f\)。
- 如果当前 \(x,y\) 同块,且 \(f_x\not=f_y\):说明 \(x,y\) 的 \(\operatorname{LCA}\) 在这个块中或就是 \(f_x\),可以不断让后面那个点往前跳 \(a\) 直至两点相同。
假设块大小为 \(\sqrt n\)。前两种情况因为跳 \(f\) 导致块编号减小,复杂度 \(O(\sqrt n)\);最后一种情况因为只会在一个块内跳,复杂度也是 \(O(\sqrt n)\)。
分块维护修改
我们可以不在修改时考虑向 \(1\) 取 \(\max\) 的操作,而是在询问时比较,这样一来修改操作就变成了简单的区间减法。
然后发现如果 \(a_i\) 本来就不在块中,\(f_i=a_i\),和 \(a_i\) 一样减少 \(x\)。
如果 \(a_i\) 在块中,由于至多减去 \(\sqrt n\) 后 \(a_i\) 就不可能在块中,也就是至多被操作 \(\sqrt n\) 次后就会被归到上一种情况。
所以对于整块修改可以直接打标记,并特殊维护出 \(a_i\) 在块内的节点暴力修改。
散块直接暴力改后重构即可。
代码:\(O(n\sqrt n)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Rg register
#define RI Rg int
#define Cn const
#define CI Cn int&
#define I inline
#define W while
#define N 400000
#define LL long long
using namespace std;
int n;LL a[N+5];
namespace FastIO
{
#define FS 100000
#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
#define pc(c) (FC==FE&&(clear(),0),*FC++=c)
int OT;char oc,FI[FS],FO[FS],OS[FS],*FA=FI,*FB=FI,*FC=FO,*FE=FO+FS;
I void clear() {fwrite(FO,1,FC-FO,stdout),FC=FO;}
Tp I void read(Ty& x) {x=0;W(!isdigit(oc=tc()));W(x=(x<<3)+(x<<1)+(oc&15),isdigit(oc=tc()));}
Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
Tp I void writeln(Ty x) {W(OS[++OT]=x%10+48,x/=10);W(OT) pc(OS[OT--]);pc('\n');}
}using namespace FastIO;
namespace Block
{
#define BS 320
#define BT 1280
int sz,bl[N+5],p[BT+5],q[BT+5];LL f[N+5],tg[BT+5];vector<int> g[BT+5];
queue<int> tmp[BS+5];I void Bd(CI o)//重构块
{
RI i,x,l=(o-1)*sz+1,r=min(o*sz,n);for(i=l;i<=r;++i) (a[i]-=tg[o])>=l?(tmp[a[i]-l].push(i),0):f[i]=a[i];tg[o]=0;//传下标记,a在块内的节点扔入对应桶
for(p[o]=q[o]=0,g[o].clear(),i=l;i<=r;++i) W(!tmp[i-l].empty()) ++q[o],g[o].push_back(x=tmp[i-l].front()),tmp[i-l].pop(),f[x]=f[i];//按顺序维护a在块内的节点
}
I void BF(CI l,CI r,CI v)//散块
{
for(RI i=l;i<=r;++i) a[i]-=v;Bd(bl[l]);//暴力修改后重构
}
I void Tg(CI o,CI v)//整块
{
RI l=(o-1)*sz+1;tg[o]+=v;W(p[o]^q[o]&&a[g[o][p[o]]]-tg[o]<l) f[g[o][p[o]]]=a[g[o][p[o]]],++p[o];//整块打标记;a变得不在块中的特殊点,归为一般情况
for(RI i=p[o];i^q[o];++i) f[g[o][i]]=f[a[g[o][i]]-tg[o]];//a仍在块中的特殊点,暴力修改
}
I void U(CI l,CI r,CI v)//分块修改
{
RI L=bl[l],R=bl[r];if(L==R) return BF(l,r,v);BF(l,L*sz,v),BF((R-1)*sz+1,r,v);for(RI i=L+1;i<R;++i) Tg(i,v);
}
I int Q(RI x,RI y)//询问LCA
{
#define F(x) max(f[x]-tg[bl[x]],1LL)
RI t;W(bl[x]^bl[y]||F(x)^F(y)) x<y&&(swap(x,y),0),bl[x]^bl[y]?x=F(x):(x=F(x),y=F(y));//不在同一块中或LCA不在当前块中
W(x^y) if(x<y&&(swap(x,y),0),(t=max(a[x]-tg[bl[x]],1LL))==F(x)) return t;else x=t;return x;//确定在当前块中或就是f[x],不断往前跳
}
}using namespace Block;
int main()
{
RI Qt,i,op,l,r,x;for(read(n,Qt),sz=sqrt(n)/2,bl[1]=1,i=2;i<=n;++i) read(a[i]),bl[i]=(i-1)/sz+1;for(i=1;i<=bl[n];++i) Bd(i);
RI t=0;W(Qt--) read(op,l,r),l^=t,r^=t,op==1?(read(x),U(l,r,x^t)):writeln(t=Q(l,r));return clear(),0;
}