题目链接:H. Yuezheng Ling and Dynamic Tree
题目大意:给定一棵大小为 (n) 的一 (1) 为根节点的树,树用如下方式给出:输入 (a_2,a_3,dots,a_n),保证 (1leq a_i<i),将 (a_i) 与 (i) 连边形成一棵树。
接下来有两种操作:
1 l r x
令 (a_i=max(a_i-x,1)(lleq ileq r))。2 u v
查询在当前的 (a) 数组构成的树上 (u,v) 的 LCA。
两种操作总数一共有 (q) 个。
(2leq n,qleq 10^5),(2leq lleq rleq n),(1leq xleq 10^5),(1leq u,vleq n)
题解:将 (a) 序列分块,假设块长为 (B),对于每一个节点,预处理出它的祖先中编号最大的和它不再同一个块的祖先,为了方便,在下文中我们称其为当前点的前驱,我们先考虑如何求两个点的 LCA。
分两种情况考虑:
- (u,v) 不属于同一个块:将属于编号较大的一个块的节点跳至它的前驱。
- (u,v) 属于同一个块:接下来还是要分两种情况:
- (u) 的前驱和 (v) 的前驱不同:将 (u,v) 同时跳至各自的前驱。
- (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(nsqrt{n}+qsqrt{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
",query(u,v));
}
}
return 0;
}