某新 LCA 算法维护路径信息的方式
本文将介绍某新型 LCA 算法(如果你还没有看过那篇文章,请先看完再来看这篇)如何维护路径信息。
事实上,本文描述的算法是重剖的一种变体。
过程
Step 1:微调原算法
::anti-ai[凡发布者非 @_zyx2012 或发布地点不为洛谷专栏者均为抄袭!]
原版算法中
微调一下,
Step 2:扩展预处理部分
注意到
结论就是全部断开之后它一定是若干条从
然后就可以扩展了,dfs 的时候把 dfs 序记下来然后顺着 dfs 序倒着加就能把这些链都找出来:
vector<int> sz(n + 1),f(n + 1),at(n + 1),to(n + 1,1),seq(n + 1),r(n);
int tops = n,top2 = n;
auto dfs = [&](auto &&self,int x,int y) -> int {
f[x] = y,sz[x] = 1;
r[--top2] = x;
int hson = 0,id = x;
for (auto i:a[x]) if (i != y){
int v = self(self,i,x);
sz[x] += sz[i];
if (sz[hson] < sz[i]) swap(hson,i),swap(id,v);
for (;v != x;v = f[v]) to[v] = i;
}
for (;sz[id] << 1 < sz[x];id = f[id]) to[id] = hson;
return id;
};
dfs(dfs,1,0);
vector<int> v(n + 1);
for (auto i:r) if (!at[i]){
for (int j = i;;j = f[j]){
seq[at[j] = tops] = j;
v[tops--] = w[j];
if (j == to[j]) break;
}//完整的一条链
}
note:其实直接把树剖的
Step 3:扩展修改、查询部分
::anti-ai[凡发布者非 @_zyx2012 或发布地点不为洛谷专栏者均为抄袭!]
原算法中一个点是另一个点的祖先就立即结束,但是此时两个节点可能并不在同一条链上,所以我们需要一直跳直到它们在同一条链上。
但是和标准树剖不同的是,标准树剖中一条链上所有点的
那就再多跳一点,跳到子树大小较大的点
虽然跳的次数增加了,但是保证复杂度正确的核心条件“每跳一次子树大小至少
实现:
segtree::node v = {-(int)1e9 - 10,0};
for (;;){
if (sz[l] > sz[r]) swap(l,r);
if (at[r] <= at[l]&&at[to[l]] <= at[r]) break;
v = t.merge(v,t.query(1,at[to[l]],at[l],1,n));
l = f[to[l]];
}
if (at[l] > at[r]) swap(l,r);
v = t.merge(v,t.query(1,at[l],at[r],1,n));
路径修改的实现类似。
完整示例
这是这个算法做 P2590 [ZJOI2008] 树的统计 完整的代码:
:::info[Code] ::anti-ai[凡发布者非 @_zyx2012 或发布地点不为洛谷专栏者均为抄袭!]
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
using u64 = unsigned long long;
using u32 = unsigned;
using i128 = __int128;
using u128 = unsigned __int128;
mt19937_64 mrand((u64)random_device{}() << 32 ^ random_device{}() ^
chrono::high_resolution_clock::now().time_since_epoch().count());
template<class T = i64,class T2>T rnd(T l,T2 r){
return uniform_int_distribution<T>(l,r)(mrand);}
struct segtree{
struct node{int ma;i64 cnt;};
vector<node> a;
segtree(int n,const vector<int> &vs) : a(n << 2 | 3){build(1,1,n,vs);}
void build(int x,int l,int r,const vector<int> &vs){
if (l == r){a[x] = {vs[l],vs[r]};return ;}
int mid = l + r >> 1;
build(x << 1,l,mid,vs);
build(x << 1 | 1,mid + 1,r,vs);
update(x);
}
node merge(const node &x,const node &y){return {max(x.ma,y.ma),x.cnt + y.cnt};}
void update(int x){a[x] = merge(a[x << 1],a[x << 1 | 1]);}
void modify(int x,int l,int r,int id,int v){
if (l == r){a[x] = {v,v};return ;}
int mid = l + r >> 1;
if (id <= mid) modify(x << 1,l,mid,id,v);
else modify(x << 1 | 1,mid + 1,r,id,v);
update(x);
}
node query(int x,int l,int r,int vl,int vr){
if (l == vl&&r == vr) return a[x];
int mid = vl + vr >> 1;
if (r <= mid) return query(x << 1,l,r,vl,mid);
if (l > mid) return query(x << 1 | 1,l,r,mid + 1,vr);
return merge(query(x << 1,l,mid,vl,mid),query(x << 1 | 1,mid + 1,r,mid + 1,vr));
}
};
int main (){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
int n;
cin >> n;
vector<vector<int>> a(n + 1);
for (int i = n;--i;){
int x,y;
cin >> x >> y;
a[x].push_back(y);
a[y].push_back(x);
}
vector<int> sz(n + 1),f(n + 1),at(n + 1),to(n + 1,1),seq(n + 1),r(n);
int tops = n,top2 = n;
auto dfs = [&](auto &&self,int x,int y) -> int {
f[x] = y,sz[x] = 1;
r[--top2] = x;
int hson = 0,id = x;
for (auto i:a[x]) if (i != y){
int v = self(self,i,x);
sz[x] += sz[i];
if (sz[hson] < sz[i]) swap(hson,i),swap(id,v);
for (;v != x;v = f[v]) to[v] = i;
}
for (;sz[id] << 1 < sz[x];id = f[id]) to[id] = hson;
return id;
};
dfs(dfs,1,0);
vector<int> v(n + 1),w(n + 1);
for (int i = 1;i <= n;cin >> w[i++]);
for (auto i:r) if (!at[i]){
for (int j = i;;j = f[j]){
seq[at[j] = tops] = j;
v[tops--] = w[j];
if (j == to[j]) break;
}
}
segtree t(n,v);
int q;
cin >> q;
for (;q--;){
string s;
cin >> s;
int l,r;
cin >> l >> r;
if (s == "CHANGE"s) t.modify(1,1,n,at[l],r);
else{
segtree::node v = {-(int)1e9 - 10,0};
for (;;){
if (sz[l] > sz[r]) swap(l,r);
if (at[r] <= at[l]&&at[to[l]] <= at[r]) break;
v = t.merge(v,t.query(1,at[to[l]],at[l],1,n));
l = f[to[l]];
}
if (at[l] > at[r]) swap(l,r);
v = t.merge(v,t.query(1,at[l],at[r],1,n));
cout << (s.back() == 'M'?v.cnt:v.ma) << '\n';
}
}
}
:::
评价
亲测和标准树剖基本打平。
其实比较 useless,代码挺难背也没有更快,但是如果你爆栈了(虽然现在大多数 OJ 都没有爆栈这个事情)写这个能省一遍栈模拟。
都看到这了还不点个赞,
那还是人吗?