Codeforces 1491H. Yuezheng Ling and Dynamic Tree 题解

题目链接:H. Yuezheng Ling and Dynamic Tree

题目大意:给定一棵大小为 \(n\) 的一 \(1\) 为根节点的树,树用如下方式给出:输入 \(a_2,a_3,\dots,a_n\),保证 \(1\leq a_i<i\),将 \(a_i\)\(i\) 连边形成一棵树。

接下来有两种操作:

  • 1 l r x\(a_i=\max(a_i-x,1)(l\leq i\leq r)\)
  • 2 u v 查询在当前的 \(a\) 数组构成的树上 \(u,v\) 的 LCA。

两种操作总数一共有 \(q\) 个。

\(2\leq n,q\leq 10^5\)\(2\leq l\leq r\leq n\)\(1\leq x\leq 10^5\)\(1\leq u,v\leq n\)


题解:将 \(a\) 序列分块,假设块长为 \(B\),对于每一个节点,预处理出它的祖先中编号最大的和它不再同一个块的祖先,为了方便,在下文中我们称其为当前点的前驱,我们先考虑如何求两个点的 LCA。

分两种情况考虑:

  1. \(u,v\) 不属于同一个块:将属于编号较大的一个块的节点跳至它的前驱。
  2. \(u,v\) 属于同一个块:接下来还是要分两种情况:
    1. \(u\) 的前驱和 \(v\) 的前驱不同:将 \(u,v\) 同时跳至各自的前驱。
    2. \(u\) 的前驱和 \(v\) 的前驱相同:此时可以轮流跳 \(u,v\) 中编号较大的点的父亲直至两个点相等。

接下来分析查询的时间复杂度:因为前驱最多不超过 \(\frac{n}{B}\) 个,而暴力跳父亲不超过 \(B\) 次,所以时间复杂度是 \(O(B+\frac{n}{B})\)

然后我们再来考虑修改:按照序列分块的惯例,我们分整块和散块来考虑,对于散块,直接暴力修改然后暴力重构前驱即可。

但是对于整块,我们需要打懒惰标记,但是这种时候无法维护前驱的修改,因为修改一个块的前驱是需要遍历整块的。

但是我们发现,一个块最多在经过 \(B\) 次修改之后,块中每一个数的父亲都会在这个块之前,也就是说,我们对于一个块需要暴力重构前驱的次数最多是 \(B\) 次,之后的可以直接通过懒标记直接减来解决。

那么分析修改的复杂度,对于散块修改是 \(O(B)\) 的,对于整块修改(不考虑重构前驱)是 \(O(\frac{n}{B})\) 的,对于重构前驱的总时间复杂度是 \(O(nB)\) 的。

综上,若取 \(B=\sqrt{n}\),则可以获得 \(O(n\sqrt{n}+q\sqrt{n})\) 的时间复杂度,可以通过本题。

代码:

#include <cstdio>
#include <algorithm>
const int Maxb=300;
const int Maxn=100000;
const int Maxv=(Maxn-1)/Maxb+1;
int n,q;
int num[Maxv+5],lazy[Maxv+5];
int fa[Maxn+5],out[Maxn+5];
int find_belong(int x){
	return (x-1)/Maxb+1;
}
int find_bel_l(int x){
	return (x-1)*Maxb+1;
}
int find_bel_r(int x){
	return std::min(n,x*Maxb);
}
void build(int x){
	for(int i=find_bel_l(x);i<=find_bel_r(x);i++){
		fa[i]=std::max(1,fa[i]-lazy[x]);
	}
	lazy[x]=0;
	for(int i=find_bel_l(x);i<=find_bel_r(x);i++){
		if(fa[i]<find_bel_l(x)){
			out[i]=fa[i];
		}
		else{
			out[i]=out[fa[i]];
		}
	}
}
int find_out(int x){
	return std::max(1,out[x]-lazy[find_belong(x)]);
}
int find_fa(int x){
	return std::max(1,fa[x]-lazy[find_belong(x)]);
}
void update(int l,int r,int x){
	int bel_l=find_belong(l),bel_r=find_belong(r);
	if(bel_l==bel_r){
		for(int i=l;i<=r;i++){
			fa[i]=std::max(1,fa[i]-x);
		}
		build(bel_l);
		return;
	}
	for(int i=l;i<=find_bel_r(bel_l);i++){
		fa[i]=std::max(1,fa[i]-x);
	}
	build(bel_l);
	for(int i=find_bel_l(bel_r);i<=r;i++){
		fa[i]=std::max(1,fa[i]-x);
	}
	build(bel_r);
	for(int i=bel_l+1;i<bel_r;i++){
		num[i]++;
		lazy[i]=std::min(n,lazy[i]+x);
		if(num[i]<=Maxb){
			build(i);
		}
	}
}
int query(int u,int v){
	while(u!=v){
		if(find_belong(u)<find_belong(v)){
			std::swap(u,v);
		}
		if(find_belong(u)>find_belong(v)){
			u=find_out(u);
		}
		else{
			if(find_out(u)!=find_out(v)){
				u=find_out(u);
				v=find_out(v);
			}
			else{
				while(u!=v){
					if(u<v){
						std::swap(u,v);
					}
					u=find_fa(u);
				}
			}
		}
	}
	return u;
}
int main(){
	scanf("%d%d",&n,&q);
	for(int i=2;i<=n;i++){
		scanf("%d",&fa[i]);
	}
	for(int i=1;i<=find_belong(n);i++){
		build(i);
	}
	for(int i=1;i<=q;i++){
		int op;
		scanf("%d",&op);
		if(op==1){
			int l,r,x;
			scanf("%d%d%d",&l,&r,&x);
			update(l,r,x);
		}
		else{
			int u,v;
			scanf("%d%d",&u,&v);
			printf("%d\n",query(u,v));
		}
	}
	return 0;
}
posted @ 2021-03-01 10:12  with_hope  阅读(225)  评论(4编辑  收藏  举报