P7446 [Ynoi2007] rfplca
原题 & CF 双倍经验, 16th
题意
给定一棵以
1 为根的树fa_2,\cdots,fa_n 与m 个操作,这里满足fa_i\lt i 操作 1: 给定区间
[l,r] ,将这个区间的fa_i\gets \max(fa_i-x,1)
操作 2: 求两点最近公共祖先
题解
建议先写 P3203 [HNOI2010]弹飞绵羊 的分块做法。这里可参考的一点是:维护
虽然这个题看起来是树上的重构与 LCA,但是和 树剖/LCT 等树上算法没有关系。
考虑分块,对于每个位置维护
那么转化求 LCA 的过程:定义 小跳 为
- 如果两个点大跳后非同一块,祖先编号大的大跳
- 如果两个点大跳后同块而非同一点,祖先编号大的大跳。
- 否则让编号大的小跳
- 两点相同时这个点即为 LCA
这样求 LCA,比较容易和修改兼容,仔细观察还会发现和树剖类似(
对于修改,小块重构,但是注意:修改
好,其实不是这样的。注意到因为它给出的树的方法与修改树的方法都比较特别,所以得知
于是我们维护
代码
这里放一个比较 lj 的实现。
long long 很有必要,,
#include<stdio.h>
#include<math.h>
#include<vector>
namespace IO{ } // by OneZzz6174
using IO::read;
using IO::readc;
using IO::print;
using IO::flush;
#define rint register int
typedef long long ll;
inline int max2(rint a,rint b){ return a>b?a:b; }
inline int min2(rint a,rint b){ return a<b?a:b; }
inline void swap2(rint &a,rint &b){ a^=b^=a^=b; }
#define rep(i,a,b) for(rint i = (a);i <= (b);++i)
const int maxn = 4e5 + 5;
const int sqr = sqrt(maxn) + 5;
int n,m,b,c;
int st[sqr],ed[sqr],bl[maxn];
int pa[maxn],fa[maxn];
ll tag[sqr]; // 这个要开 long long
int chg[maxn];
void init(){ // 预处理分块的 st,ed,bl 数组
b = sqrt(n); c = (n - 1) / b + 1;
rep(i,1,c){
st[i] = (i - 1) * b + 1;
ed[i] = i == c ? n : i * b;
rep(j,st[i],ed[i]) bl[j] = i;
}
}
#define C(i) (chg[i] <= b) // 这一块再修改是否需要重构
#define Pa(x) (C(bl[x]) ? pa[x] : max2(1,fa[x] - tag[bl[x]]))
#define Fa(x) (C(bl[x]) ? fa[x] : max2(1,fa[x] - tag[bl[x]]))
// pa,fa 数组只有在 chg[bl[i]]<=b 的时候才有用,所以这里写一个在任何时候都有用的 pa,fa 询问
void chg1(int i){ // 单独更新 1 个点的 pa 值,这种写法可以的原因是 fa[i]<i
if(C(bl[i])) pa[i] = bl[i] ^ bl[fa[i]] ? fa[i] : pa[fa[i]];
}
void chg2(int i,int x){ // 修改一个块
if(!C(i)) return void(tag[i] = min2(tag[i] + x,n)); // 如果不用重构,那么加 tag
rep(j,st[i],ed[i]) fa[j] = max2(1,fa[j] - x),chg1(j); // 重构
}
void upd0(int l,int r,int x){ // 修改零散块
rep(i,l,r) fa[i] = max2(1,fa[i] - x); // 修改 l~r
rep(i,l,ed[bl[r]]) chg1(i); // 要重构的位置是 l~end[bl[r]] !!!!!
// 原因:前面的修改可能会对这个块后面的位置产生影响
}
void upd(int l,int r,int x){ // 真正的修改函数
if(bl[l] == bl[r]) return upd0(l,r,x); // 零散块
upd0(l,ed[bl[l]],x); upd(st[bl[r]],r,x); // 零散块
rep(i,bl[l] + 1,bl[r] - 1) chg2(i,x),++chg[i]; // 整块暴力重构/打tag
}
int qry(int u,int v){ // 求 LCA
while(u ^ v){ // u=v 时即为答案
int pu = Pa(u),pv = Pa(v); // 找到第一个祖先
if(bl[pu] ^ bl[pv]) bl[pu] < bl[pv] ? v = pv : u = pu;
// 祖先不在同一块,深度大的必然是编号大的,让它先跳
else if(pu ^ pv) pu > pv ? u = pu : v = pv;
// 祖先在同一块,深度大的也必然是编号大的
else u > v ? u = Fa(u) : v = Fa(v);
// 编号大的来小跳
}
return u;
}
signed main(){
read(n,m);
fa[1] = pa[1] = 1; /* this */ init();
rep(i,2,n) fa[i] = read(),chg1(i);
int op,l,r,lst = 0;
while(m--){
read(op,l,r); l ^= lst,r ^= lst;
op == 1 ? upd(l,r,read() ^ lst) : print(lst = qry(l,r),'\n');
}
return flush(),0;
}