把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【洛谷7446】[Ynoi2007] rfplca(分块)

题目链接

  • 有一棵 \(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;
}
posted @ 2021-11-12 18:28  TheLostWeak  阅读(47)  评论(0编辑  收藏  举报