可持久化线段树
zhuxi树(可持久化权值线段树)完全指南
第一章 可持久化数据结构概述
1.1 什么是可持久化
可持久化(Persistent) 是指数据结构能够保留其所有历史版本的状态,允许在任意时刻查询任意历史版本的信息,同时支持从某个历史版本派生出新版本。可持久化的核心思想是路径复制(Path Copying) :每次修改只复制被修改节点到根路径上的所有节点,未修改的部分则与旧版本共享。
1.2 zhuxi树的由来
zhuxi树全称是可持久化权值线段树(Persistent Weighted Segment Tree) ,因其引入者姓名缩写与某位国家zhuxi相同,故在中文OI圈被称为“zhuxi树”。它是一种基于权值线段树的可持久化数据结构,主要用于解决区间第 \(k\) 小/大值问题。
1.3 zhuxi树与普通线段树的区别
普通线段树在修改时会直接修改原有节点;而zhuxi树在修改时不会修改任何已有节点,而是创建新节点,新节点继承旧节点的左右儿子指针,仅修改的路径上的节点被复制并更新。
第二章 静态zhuxi树:区间第k小
2.1 问题模型
给定长度为 \(n\) 的序列 \(a_1, a_2, \dots, a_n\),\(m\) 次询问,每次询问区间 \([l, r]\) 中的第 \(k\) 小值。
2.2 核心思想
利用前缀和的思想:为每个前缀 \([1, i]\) 建立一棵权值线段树,第 \(i\) 棵线段树中存储了 \(a_1 \dots a_i\) 中每个值出现的次数。那么区间 \([l, r]\) 的权值线段树 = 第 \(r\) 棵线段树 - 第 \(l-1\) 棵线段树。
在查询时,我们从两棵线段树的根同时出发,比较左子树的差值来确定第 \(k\) 小在左子树还是右子树中。
2.3 离散化
由于权值可能很大,需要先对原数组离散化,将值映射到 \(1 \sim n\) 的范围内,这样权值线段树的大小就是 \(O(n)\) 的。
2.4 建树与插入
设 \(root_i\) 为第 \(i\) 个版本的根节点编号。插入第 \(i\) 个数时,从 \(root_{i-1}\) 复制路径,创建一个新版本 \(root_i\)。每次插入会新建 \(O(\log n)\) 个节点。
2.5 查询第k小
设当前查询区间为 \([l, r]\),当前节点区间为 \([L, R]\),令 \(mid = \frac{L+R}{2}\)。令 \(u = root_r\),\(v = root_{l-1}\),左子树中的元素个数为 \(cnt = tree[tree[u].ls].sum - tree[tree[v].ls].sum\)。
- 若 \(k \le cnt\),则第 \(k\) 小在左子树中,递归查询左子树;
- 否则第 \(k\) 小在右子树中,递归查询右子树,且 \(k \leftarrow k - cnt\)。
当 \(L = R\) 时返回 \(L\)。
2.6 复杂度分析
- 建树:\(O(n \log n)\)
- 查询:\(O(\log n)\)
- 空间:\(O(n \log n)\)
2.7 模板代码(洛谷 P3834)
#include <bits/stdc++.h>
#define int long long
#define COUT cout<<"\n------------------------------------------------------------------------------------------\n";
#define fi first
#define se second
#define be begin()
#define en end()
using namespace std;
const int N=2e5+5;
const int mod=998244353;
const int inf=1e18;
int n,m,a[N],b[N],root[N],tot;
struct Node{int ls,rs,sum;}tr[N*32];
void update(int&u,int v,int l,int r,int x)
{
u=++tot;
tr[u]=tr[v];
tr[u].sum++;
if(l==r)return;
int mid=(l+r)>>1;
if(x<=mid)update(tr[u].ls,tr[v].ls,l,mid,x);
else update(tr[u].rs,tr[v].rs,mid+1,r,x);
}
int query(int u,int v,int l,int r,int k)
{
if(l==r)return l;
int mid=(l+r)>>1;
int cnt=tr[tr[u].ls].sum-tr[tr[v].ls].sum;
if(k<=cnt)return query(tr[u].ls,tr[v].ls,l,mid,k);
else return query(tr[u].rs,tr[v].rs,mid+1,r,k-cnt);
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++){cin>>a[i];b[i]=a[i];}
sort(b+1,b+n+1);
int len=unique(b+1,b+n+1)-b-1;
for(int i=1;i<=n;i++)
{
int p=lower_bound(b+1,b+len+1,a[i])-b;
update(root[i],root[i-1],1,len,p);
}
while(m--)
{
int l,r,k;
cin>>l>>r>>k;
int p=query(root[r],root[l-1],1,len,k);
cout<<b[p]<<"\n";
}
return 0;
}
第三章 zhuxi树的数学基础
3.1 前缀和定理
定理 3.1(前缀和可减性) 设 \(T_i\) 为前 \(i\) 个元素构成的权值线段树,则区间 \([l, r]\) 的权值线段树 \(T_{[l,r]}\) 满足:
即对任意节点,其值为 \(T_r\) 中该节点的值减去 \(T_{l-1}\) 中该节点的值。
证明:\(T_i\) 中每个叶节点存储值 \(x\) 在前 \(i\) 个元素中出现的次数。设 \(f_i(x)\) 为 \(x\) 在前 \(i\) 个元素中出现的次数,则 \(f_r(x) - f_{l-1}(x)\) 正是 \(x\) 在 \([l, r]\) 中出现的次数。证毕。
3.2 zhuxi树的空间复杂度定理
定理 3.2(空间复杂度) 对长度为 \(n\) 的序列建立zhuxi树,若每次插入创建 \(O(\log n)\) 个节点,则总节点数为 \(O(n \log n)\)。
推论 3.1 对于 \(n \le 2 \times 10^5\) 的题目,zhuxi树数组通常开 \(n \times 32\) 或 \(n \times 40\) 大小。
3.3 第k小查询的正确性定理
定理 3.3 设区间 \([l, r]\) 的权值线段树上,左子树中元素个数为 \(c\)。若 \(k \le c\),则第 \(k\) 小在左子树;否则第 \(k-c\) 小在右子树。
证明:权值线段树按值域划分,左子树对应的值域全部小于右子树对应的值域。左子树中有 \(c\) 个元素,它们分别是第 \(1, 2, \dots, c\) 小。若 \(k \le c\),则第 \(k\) 小必然在左子树中;否则第 \(k\) 小是右子树中的第 \(k-c\) 小。证毕。
第四章 zhuxi树的常见变形
4.1 可持久化数组
可持久化数组是最基础的可持久化应用:支持单点修改,并保留历史版本,可在任意版本查询任意位置的值。实现时以数组下标为版本,用zhuxi树维护。
4.2 带修zhuxi树(树状数组套zhuxi树)
问题:支持单点修改,同时查询区间第 \(k\) 小。
核心思想:静态zhuxi树利用前缀和建树,修改 \(a_i\) 会影响 \(root_i, root_{i+1}, \dots, root_n\),复杂度不可接受。改用树状数组维护权值线段树的前缀和:树状数组的每个节点 \(C_i\) 维护一棵权值线段树,管理区间 \([i - \text{lowbit}(i) + 1, i]\)。
查询:要得到 \([l, r]\) 的权值线段树,需要求出 \(r\) 处的前缀和与 \(l-1\) 处的前缀和。树状数组前缀和查询会分解为 \(O(\log n)\) 棵线段树,将这些线段树的根记录下来,在查询时同步二分。
复杂度:修改 \(O(\log^2 n)\),查询 \(O(\log^2 n)\)。
4.3 树上zhuxi树(路径第k小)
问题:给定一棵树,每个节点有权值,查询 \(u\) 到 \(v\) 路径上的第 \(k\) 小点权。
核心思想:从根节点 DFS,为每个节点 \(u\) 建立从其父节点 \(fa[u]\) 的zhuxi树继承而来的一棵新树。这样 \(root_u\) 就代表了从根到 \(u\) 路径上所有点的权值线段树。
查询 \(u \to v\) 路径时,设 \(lca = \text{LCA}(u, v)\),则路径上的权值线段树为:
即四个版本同时参与查询。
公式(查询时左子树的元素个数):
4.4 可持久化Trie(01-Trie)
可持久化Trie与zhuxi树的思想完全一致:每次插入时只复制路径上的节点。常用于解决区间最大异或和问题。
问题:给定区间 \([l, r]\) 和一个数 \(x\),求 \(a_p \oplus x\) 的最大值(\(l \le p \le r\))。
做法:为每个前缀建立一棵可持久化01-Trie,第 \(i\) 棵Trie包含 \(a_1 \dots a_i\)。查询时用第 \(r\) 棵和第 \(l-1\) 棵做差,从高位到低位贪心选择与 \(x\) 当前位相反的路径。
4.5 zhuxi树+区间合并(连续1)
某些问题需要维护区间内连续1的最长长度。线段树的每个节点维护三个值:
- \(lmx\):从左端点开始的最长连续1长度
- \(rmx\):从右端点结束的最长连续1长度
- \(mx\):区间内最长连续1长度
合并公式:
配合zhuxi树可以预处理出多个阈值下的01序列。
4.6 zhuxi树与二维数点
zhuxi树天然适合解决强制在线二维数点问题。以下标为版本,以权值为线段树下标,查询区间 \([l, r]\) 中值在 \([x, y]\) 范围内的元素个数。
4.7 可持久化并查集
用zhuxi树维护并查集的 \(fa\) 数组,每次合并时新建一个版本。不能使用路径压缩(因为路径压缩会修改多个节点),需使用按秩合并保证复杂度为 \(O(\log n)\)。
第五章 典型例题精讲
5.1 abc453g G - Copy Query
题意:有 \(N\) 个长度为 \(M\) 的整数列,初始全为0。三种操作:
- 将数列 \(X\) 复制为数列 \(Y\)(\(R_X \leftarrow R_Y\))
- 将数列 \(X\) 的第 \(p\) 个元素改为 \(v\)
- 求数列 \(X\) 区间 \([l, r]\) 的和
思路:维护每个数列对应的线段树根节点 \(R_i\)。操作1直接令 \(R_X = R_Y\);操作2在 \(R_X\) 基础上单点修改生成新根;操作3区间查询。
#include <bits/stdc++.h>
#define int long long
#define COUT cout<<"\n------------------------------------------------------------------------------------------\n";
#define fi first
#define se second
#define be begin()
#define en end()
using namespace std;
const int N=2e5+5;
const int mod=998244353;
const int inf=1e18;
int n,m,q,tot;
int root[N];
struct Node{int ls,rs,sum;}tr[N*40];
void update(int&u,int v,int l,int r,int pos,int val)
{
u=++tot;
tr[u]=tr[v];
tr[u].sum+=val;
if(l==r)return;
int mid=(l+r)>>1;
if(pos<=mid)update(tr[u].ls,tr[v].ls,l,mid,pos,val);
else update(tr[u].rs,tr[v].rs,mid+1,r,pos,val);
}
int query(int u,int l,int r,int ql,int qr)
{
if(!u||ql>r||qr<l)return 0;
if(ql<=l&&r<=qr)return tr[u].sum;
int mid=(l+r)>>1;
return query(tr[u].ls,l,mid,ql,qr)+query(tr[u].rs,mid+1,r,ql,qr);
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>m>>q;
for(int i=1;i<=n;i++)root[i]=0;
while(q--)
{
int op,x;
cin>>op>>x;
if(op==1)
{
int y;
cin>>y;
root[x]=root[y];
}
else if(op==2)
{
int p,v;
cin>>p>>v;
update(root[x],root[x],1,m,p,v);
}
else
{
int l,r;
cin>>l>>r;
cout<<query(root[x],1,m,l,r)<<"\n";
}
}
return 0;
}
5.2 jzyz522 静态区间第k小
标准静态zhuxi树模板,见第二章模板代码。
5.3 cses3163 Range Interval Queries
题意:查询区间 \([l, r]\) 中值在 \([c, d]\) 之间的元素个数。
思路:zhuxi树以数组下标为版本,权值为线段树下标。查询时统计第 \(r\) 棵和第 \(l-1\) 棵在值域 \([c, d]\) 上的差值。
#include <bits/stdc++.h>
#define int long long
#define COUT cout<<"\n------------------------------------------------------------------------------------------\n";
#define fi first
#define se second
#define be begin()
#define en end()
using namespace std;
const int N=2e5+5;
const int mod=998244353;
const int inf=1e18;
int n,m,a[N],b[N],root[N],tot;
struct Node{int ls,rs,sum;}tr[N*32];
void update(int&u,int v,int l,int r,int x)
{
u=++tot;
tr[u]=tr[v];
tr[u].sum++;
if(l==r)return;
int mid=(l+r)>>1;
if(x<=mid)update(tr[u].ls,tr[v].ls,l,mid,x);
else update(tr[u].rs,tr[v].rs,mid+1,r,x);
}
int query(int u,int v,int l,int r,int ql,int qr)
{
if(ql>r||qr<l)return 0;
if(ql<=l&&r<=qr)return tr[u].sum-tr[v].sum;
int mid=(l+r)>>1;
return query(tr[u].ls,tr[v].ls,l,mid,ql,qr)+query(tr[u].rs,tr[v].rs,mid+1,r,ql,qr);
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++){cin>>a[i];b[i]=a[i];}
sort(b+1,b+n+1);
int len=unique(b+1,b+n+1)-b-1;
for(int i=1;i<=n;i++)
{
int p=lower_bound(b+1,b+len+1,a[i])-b;
update(root[i],root[i-1],1,len,p);
}
while(m--)
{
int l,r,c,d;
cin>>l>>r>>c>>d;
int L=lower_bound(b+1,b+len+1,c)-b;
int R=upper_bound(b+1,b+len+1,d)-b-1;
if(L>R)cout<<"0\n";
else cout<<query(root[r],root[l-1],1,len,L,R)<<"\n";
}
return 0;
}
5.4 BZOJ2588 Spoj 10628. Count on a tree
题意:树上路径第 \(k\) 小点权(强制在线)。
思路:DFS建树上zhuxi树,查询时用 \(u, v, lca, fa[lca]\) 四个版本差分。LCA使用倍增法。
#include <bits/stdc++.h>
#define int long long
#define COUT cout<<"\n------------------------------------------------------------------------------------------\n";
#define fi first
#define se second
#define be begin()
#define en end()
using namespace std;
const int N=1e5+5;
const int mod=998244353;
const int inf=1e18;
int n,m;
int val[N],b[N],root[N],tot;
int up[N][20],dep[N];
vector<int>g[N];
struct Node{int ls,rs,sum;}tr[N*40];
void update(int&u,int v,int l,int r,int x)
{
u=++tot;
tr[u]=tr[v];
tr[u].sum++;
if(l==r)return;
int mid=(l+r)>>1;
if(x<=mid)update(tr[u].ls,tr[v].ls,l,mid,x);
else update(tr[u].rs,tr[v].rs,mid+1,r,x);
}
void dfs(int u,int fa)
{
up[u][0]=fa;
dep[u]=dep[fa]+1;
for(int i=1;i<20;i++)up[u][i]=up[up[u][i-1]][i-1];
int p=lower_bound(b+1,b+n+1,val[u])-b;
update(root[u],root[fa],1,n,p);
for(int v:g[u])if(v!=fa)dfs(v,u);
}
int lca(int u,int v)
{
if(dep[u]<dep[v])swap(u,v);
for(int i=19;i>=0;i--)if(dep[up[u][i]]>=dep[v])u=up[u][i];
if(u==v)return u;
for(int i=19;i>=0;i--)if(up[u][i]!=up[v][i]){u=up[u][i];v=up[v][i];}
return up[u][0];
}
int query(int u,int v,int lc,int flc,int l,int r,int k)
{
if(l==r)return l;
int mid=(l+r)>>1;
int cnt=tr[tr[u].ls].sum+tr[tr[v].ls].sum-tr[tr[lc].ls].sum-tr[tr[flc].ls].sum;
if(k<=cnt)return query(tr[u].ls,tr[v].ls,tr[lc].ls,tr[flc].ls,l,mid,k);
else return query(tr[u].rs,tr[v].rs,tr[lc].rs,tr[flc].rs,mid+1,r,k-cnt);
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++){cin>>val[i];b[i]=val[i];}
sort(b+1,b+n+1);
int len=unique(b+1,b+n+1)-b-1;
for(int i=1;i<n;i++)
{
int x,y;cin>>x>>y;
g[x].push_back(y);g[y].push_back(x);
}
dfs(1,0);
int last=0;
while(m--)
{
int u,v,k;
cin>>u>>v>>k;
u^=last;
int lc=lca(u,v);
int p=query(root[u],root[v],root[lc],root[up[lc][0]],1,len,k);
last=b[p];
cout<<last<<"\n";
}
return 0;
}
5.5 P484E Sign on Fence
题意:给定数列,每次询问区间 \([l, r]\) 内选长度为 \(w\) 的连续子区间,使子区间最小数的最大值最大。
思路:二分答案 \(mid\),将 \(\ge mid\) 的数标为1,\(< mid\) 的数标为0,判断区间内最长连续1的长度是否 \(\ge w\)。由于不同阈值下的01序列只有单点差异,用zhuxi树预处理从大到小排序后的版本。
#include <bits/stdc++.h>
#define int long long
#define COUT cout<<"\n------------------------------------------------------------------------------------------\n";
#define fi first
#define se second
#define be begin()
#define en end()
using namespace std;
const int N=1e5+5;
const int mod=998244353;
const int inf=1e18;
int n,m,tot;
int root[N];
pair<int,int>p[N];
struct Node{int ls,rs,lmx,rmx,mx;}tr[N*40];
void pushup(int u,int l,int r)
{
int mid=(l+r)>>1;
int lc=tr[u].ls,rc=tr[u].rs;
tr[u].lmx=tr[lc].lmx;
if(tr[lc].mx==mid-l+1)tr[u].lmx=mid-l+1+tr[rc].lmx;
tr[u].rmx=tr[rc].rmx;
if(tr[rc].mx==r-mid)tr[u].rmx=r-mid+tr[lc].rmx;
tr[u].mx=max({tr[lc].mx,tr[rc].mx,tr[lc].rmx+tr[rc].lmx});
}
void update(int&u,int v,int l,int r,int pos)
{
u=++tot;
tr[u]=tr[v];
if(l==r){tr[u].lmx=tr[u].rmx=tr[u].mx=1;return;}
int mid=(l+r)>>1;
if(pos<=mid)update(tr[u].ls,tr[v].ls,l,mid,pos);
else update(tr[u].rs,tr[v].rs,mid+1,r,pos);
pushup(u,l,r);
}
Node query(int u,int l,int r,int ql,int qr)
{
if(ql<=l&&r<=qr)return tr[u];
int mid=(l+r)>>1;
if(qr<=mid)return query(tr[u].ls,l,mid,ql,qr);
if(ql>mid)return query(tr[u].rs,mid+1,r,ql,qr);
Node L=query(tr[u].ls,l,mid,ql,qr);
Node R=query(tr[u].rs,mid+1,r,ql,qr);
Node res;
res.lmx=L.lmx;
if(L.mx==mid-max(l,ql)+1)res.lmx=mid-max(l,ql)+1+R.lmx;
res.rmx=R.rmx;
if(R.mx==min(r,qr)-mid)res.rmx=min(r,qr)-mid+L.rmx;
res.mx=max({L.mx,R.mx,L.rmx+R.lmx});
return res;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++){cin>>p[i].first;p[i].second=i;}
sort(p+1,p+n+1,greater<pair<int,int>>());
for(int i=1;i<=n;i++)
update(root[i],root[i-1],1,n,p[i].second);
cin>>m;
while(m--)
{
int l,r,w;
cin>>l>>r>>w;
int lo=1,hi=n,ans=1;
while(lo<=hi)
{
int mid=(lo+hi)>>1;
Node res=query(root[mid],1,n,l,r);
if(res.mx>=w){ans=mid;lo=mid+1;}
else hi=mid-1;
}
cout<<p[ans].first<<"\n";
}
return 0;
}
5.6 BZOJ1901 Dynamic Rankings / P2617 动态区间第k小
题意:支持单点修改和区间第 \(k\) 小查询。
思路:树状数组套zhuxi树。树状数组的每个节点维护一棵权值线段树,修改时更新 \(O(\log n)\) 棵线段树,查询时将 \(O(\log n)\) 棵线段树的根记录到两个数组中,同步二分。
#include <bits/stdc++.h>
#define int long long
#define COUT cout<<"\n------------------------------------------------------------------------------------------\n";
#define fi first
#define se second
#define be begin()
#define en end()
using namespace std;
const int N=2e5+5;
const int mod=998244353;
const int inf=1e18;
int n,m,tot;
int a[N],b[N],root[N];
int tmp1[N],tmp2[N],cnt1,cnt2;
struct Node{int ls,rs,sum;}tr[N*200];
struct Q{int op,l,r,k;}q[N];
int lowbit(int x){return x&-x;}
void update(int&u,int l,int r,int pos,int val)
{
if(!u)u=++tot;
tr[u].sum+=val;
if(l==r)return;
int mid=(l+r)>>1;
if(pos<=mid)update(tr[u].ls,l,mid,pos,val);
else update(tr[u].rs,mid+1,r,pos,val);
}
void modify(int x,int pos,int val)
{
for(int i=x;i<=n;i+=lowbit(i))
update(root[i],1,tot,pos,val);
}
int query(int l,int r,int k)
{
if(l==r)return l;
int mid=(l+r)>>1;
int sum=0;
for(int i=1;i<=cnt1;i++)sum+=tr[tr[tmp1[i]].ls].sum;
for(int i=1;i<=cnt2;i++)sum-=tr[tr[tmp2[i]].ls].sum;
if(k<=sum)
{
for(int i=1;i<=cnt1;i++)tmp1[i]=tr[tmp1[i]].ls;
for(int i=1;i<=cnt2;i++)tmp2[i]=tr[tmp2[i]].ls;
return query(l,mid,k);
}
else
{
for(int i=1;i<=cnt1;i++)tmp1[i]=tr[tmp1[i]].rs;
for(int i=1;i<=cnt2;i++)tmp2[i]=tr[tmp2[i]].rs;
return query(mid+1,r,k-sum);
}
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++){cin>>a[i];b[++tot]=a[i];}
for(int i=1;i<=m;i++)
{
char c;cin>>c;
if(c=='Q'){q[i].op=0;cin>>q[i].l>>q[i].r>>q[i].k;}
else{q[i].op=1;cin>>q[i].l>>q[i].r;b[++tot]=q[i].r;}
}
sort(b+1,b+tot+1);
int len=unique(b+1,b+tot+1)-b-1;
tot=len;
for(int i=1;i<=n;i++)
{
int p=lower_bound(b+1,b+len+1,a[i])-b;
modify(i,p,1);
}
for(int i=1;i<=m;i++)
{
if(q[i].op==0)
{
cnt1=cnt2=0;
for(int j=q[i].r;j;j-=lowbit(j))tmp1[++cnt1]=root[j];
for(int j=q[i].l-1;j;j-=lowbit(j))tmp2[++cnt2]=root[j];
int p=query(1,len,q[i].k);
cout<<b[p]<<"\n";
}
else
{
int pos=q[i].l,val=q[i].r;
int old=lower_bound(b+1,b+len+1,a[pos])-b;
modify(pos,old,-1);
a[pos]=val;
int ne=lower_bound(b+1,b+len+1,val)-b;
modify(pos,ne,1);
}
}
return 0;
}
5.7 jzyz1615 最大异或和(洛谷 P4735)
题意:维护序列,支持末尾添加数、查询 \([l, r]\) 中 \(a_p \oplus x\) 的最大值。
思路:可持久化01-Trie。设前缀异或和 \(s_i = a_1 \oplus \dots \oplus a_i\),查询 \(a_p \oplus \dots \oplus a_n \oplus x = s_n \oplus s_{p-1} \oplus x\)。令 \(y = s_n \oplus x\),问题转化为在 \([l-1, r-1]\) 中找 \(s_{p-1}\) 使 \(s_{p-1} \oplus y\) 最大。
#include <bits/stdc++.h>
#define int long long
#define COUT cout<<"\n------------------------------------------------------------------------------------------\n";
#define fi first
#define se second
#define be begin()
#define en end()
using namespace std;
const int N=6e5+5;
const int mod=998244353;
const int inf=1e18;
int n,m,tot;
int s[N],root[N];
int tr[N*25][2],mxid[N*25];
void insert(int&u,int v,int x,int id)
{
u=++tot;
int p=u,q=v;
for(int i=23;i>=0;i--)
{
int c=(x>>i)&1;
tr[p][c]=++tot;
tr[p][c^1]=tr[q][c^1];
p=tr[p][c];
q=tr[q][c];
mxid[p]=id;
}
}
int query(int u,int v,int x)
{
int res=0;
for(int i=23;i>=0;i--)
{
int c=(x>>i)&1;
if(mxid[tr[v][c^1]]>mxid[tr[u][c^1]]||tr[v][c^1]!=tr[u][c^1])
{
if(tr[v][c^1]&&mxid[tr[v][c^1]]>mxid[tr[u][c^1]])
{
res|=(1<<i);
u=tr[u][c^1];v=tr[v][c^1];
}
else
{
u=tr[u][c];v=tr[v][c];
}
}
else
{
u=tr[u][c];v=tr[v][c];
}
}
return res;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>m;
mxid[0]=-1;
insert(root[0],0,0,0);
for(int i=1;i<=n;i++)
{
int x;cin>>x;
s[i]=s[i-1]^x;
insert(root[i],root[i-1],s[i],i);
}
while(m--)
{
char op;cin>>op;
if(op=='A')
{
int x;cin>>x;
n++;
s[n]=s[n-1]^x;
insert(root[n],root[n-1],s[n],n);
}
else
{
int l,r,x;
cin>>l>>r>>x;
int y=s[n]^x;
cout<<query(root[l-2],root[r-1],y)<<"\n";
}
}
return 0;
}
5.8 loj2016 「SCOI2016」美味
题意:给定数列 \(a_1 \dots a_n\),每次询问 \(b, x, l, r\),求 \(\max_{i \in [l,r]} (b \oplus (a_i + x))\)。
思路:按位贪心。从高位到低位,假设当前位为 \(d\),尝试让答案的第 \(d\) 位与 \(b\) 的第 \(d\) 位不同。若 \(b\) 的第 \(d\) 位为0,则希望 \(a_i + x\) 的第 \(d\) 位为1;反之亦然。在zhuxi树上查询值域区间 \([L, R]\) 内是否存在元素。
#include <bits/stdc++.h>
#define int long long
#define COUT cout<<"\n------------------------------------------------------------------------------------------\n";
#define fi first
#define se second
#define be begin()
#define en end()
using namespace std;
const int N=2e5+5;
const int mod=998244353;
const int inf=1e18;
int n,m,a[N],root[N],tot;
struct Node{int ls,rs,sum;}tr[N*32];
void update(int&u,int v,int l,int r,int x)
{
u=++tot;
tr[u]=tr[v];
tr[u].sum++;
if(l==r)return;
int mid=(l+r)>>1;
if(x<=mid)update(tr[u].ls,tr[v].ls,l,mid,x);
else update(tr[u].rs,tr[v].rs,mid+1,r,x);
}
int query(int u,int v,int l,int r,int ql,int qr)
{
if(ql>r||qr<l)return 0;
if(ql<=l&&r<=qr)return tr[u].sum-tr[v].sum;
int mid=(l+r)>>1;
return query(tr[u].ls,tr[v].ls,l,mid,ql,qr)+query(tr[u].rs,tr[v].rs,mid+1,r,ql,qr);
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>m;
int maxv=0;
for(int i=1;i<=n;i++){cin>>a[i];maxv=max(maxv,a[i]);}
for(int i=1;i<=n;i++)update(root[i],root[i-1],0,maxv,a[i]);
while(m--)
{
int b,x,l,r;
cin>>b>>x>>l>>r;
int ans=0;
for(int d=17;d>=0;d--)
{
int want=((b>>d)&1)^1;
int base=ans|(want<<d);
int L=base-x,R=base+(1<<d)-1-x;
L=max(L,0LL);
if(L>R||query(root[r],root[l-1],0,maxv,L,R)==0)
{
int other=((b>>d)&1);
ans|=other<<d;
}
else ans=base;
}
cout<<(ans^b)<<"\n";
}
return 0;
}
5.9 abc324gL G - Generate Arrays
题意:给定一个排列,维护多个序列。操作1:将序列 \(s_i\) 中下标 \(\ge x\) 的元素分离出来形成新序列;操作2:将序列 \(s_i\) 中值 \(> x\) 的元素分离出来形成新序列。输出新序列长度。
思路:用 Wavelet Matrix 或zhuxi树维护二维点 \((i, p_i)\)。每个序列对应一个矩形区域,操作相当于在矩形内按某一维切分。用zhuxi树可以 \(O(\log n)\) 统计矩形内点数。
#include <bits/stdc++.h>
#define int long long
#define COUT cout<<"\n------------------------------------------------------------------------------------------\n";
#define fi first
#define se second
#define be begin()
#define en end()
using namespace std;
const int N=2e5+5;
const int mod=998244353;
const int inf=1e18;
int n,q,a[N],root[N],tot;
struct Node{int ls,rs,sum;}tr[N*32];
struct Seq{int xl,xr,yl,yr;};
vector<Seq>seqs;
void update(int&u,int v,int l,int r,int x)
{
u=++tot;
tr[u]=tr[v];
tr[u].sum++;
if(l==r)return;
int mid=(l+r)>>1;
if(x<=mid)update(tr[u].ls,tr[v].ls,l,mid,x);
else update(tr[u].rs,tr[v].rs,mid+1,r,x);
}
int query(int u,int v,int l,int r,int ql,int qr)
{
if(ql>r||qr<l)return 0;
if(ql<=l&&r<=qr)return tr[u].sum-tr[v].sum;
int mid=(l+r)>>1;
return query(tr[u].ls,tr[v].ls,l,mid,ql,qr)+query(tr[u].rs,tr[v].rs,mid+1,r,ql,qr);
}
int count(int xl,int xr,int yl,int yr)
{
if(xl>xr||yl>yr)return 0;
return query(root[xr],root[xl-1],1,n,yl,yr);
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++){cin>>a[i];update(root[i],root[i-1],1,n,a[i]);}
seqs.push_back({1,n,1,n});
cin>>q;
while(q--)
{
int t,s,x;
cin>>t>>s>>x;
Seq cur=seqs[s];
if(t==1)
{
int mid=cur.xl+x-1;
Seq left={cur.xl,mid,cur.yl,cur.yr};
Seq right={mid+1,cur.xr,cur.yl,cur.yr};
seqs.push_back(right);
seqs[s]=left;
cout<<right.xr-right.xl+1<<"\n";
}
else
{
int l=cur.yl,r=cur.yr;
int lo=l,hi=r,ans=r;
while(lo<=hi)
{
int mid=(lo+hi)>>1;
if(count(cur.xl,cur.xr,l,mid)<=x){ans=mid;lo=mid+1;}
else hi=mid-1;
}
Seq low={cur.xl,cur.xr,cur.yl,ans};
Seq high={cur.xl,cur.xr,ans+1,cur.yr};
seqs.push_back(high);
seqs[s]=low;
cout<<high.yr-high.yl+1<<"\n";
}
}
return 0;
}
5.10 abc405fW F - Chord Crossing
题意:圆上 \(2N\) 个点,连 \(N\) 条弦。每次询问一条新弦,求它与已有弦的交点个数。
思路:弦 \((l_1, r_1)\) 与 \((l_2, r_2)\) 相交当且仅当 \(l_1 < l_2 < r_1 < r_2\) 或 \(l_2 < l_1 < r_2 < r_1\)。问题转化为二维数点:查询满足 \(l_i < L < r_i < R\) 的弦数加上满足 \(L < l_i < R < r_i\) 的弦数。
#include <bits/stdc++.h>
#define int long long
#define COUT cout<<"\n------------------------------------------------------------------------------------------\n";
#define fi first
#define se second
#define be begin()
#define en end()
using namespace std;
const int N=2e5+5;
const int mod=998244353;
const int inf=1e18;
int n,q;
int L[N],R[N];
vector<int>vals;
int root[N],tot;
struct Node{int ls,rs,sum;}tr[N*32];
void update(int&u,int v,int l,int r,int x)
{
u=++tot;
tr[u]=tr[v];
tr[u].sum++;
if(l==r)return;
int mid=(l+r)>>1;
if(x<=mid)update(tr[u].ls,tr[v].ls,l,mid,x);
else update(tr[u].rs,tr[v].rs,mid+1,r,x);
}
int query(int u,int v,int l,int r,int ql,int qr)
{
if(ql>r||qr<l)return 0;
if(ql<=l&&r<=qr)return tr[u].sum-tr[v].sum;
int mid=(l+r)>>1;
return query(tr[u].ls,tr[v].ls,l,mid,ql,qr)+query(tr[u].rs,tr[v].rs,mid+1,r,ql,qr);
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>q;
for(int i=1;i<=n;i++){cin>>L[i]>>R[i];vals.push_back(R[i]);}
sort(vals.begin(),vals.end());
vals.erase(unique(vals.begin(),vals.end()),vals.end());
int sz=vals.size();
vector<pair<int,int>>chords;
for(int i=1;i<=n;i++)
{
int r=lower_bound(vals.begin(),vals.end(),R[i])-vals.begin()+1;
chords.push_back({L[i],r});
}
sort(chords.begin(),chords.end());
for(int i=0;i<n;i++)
update(root[i+1],root[i],1,sz,chords[i].second);
while(q--)
{
int l,r;cin>>l>>r;
int cnt1=0,cnt2=0;
int pos=lower_bound(chords.begin(),chords.end(),make_pair(l,0LL))-chords.begin();
if(pos>0)cnt1=query(root[pos],root[0],1,sz,1,sz)-query(root[pos],root[0],1,sz,1,r);
int pos2=lower_bound(chords.begin(),chords.end(),make_pair(r,0LL))-chords.begin();
if(pos2>pos)cnt2=query(root[pos2],root[pos],1,sz,1,sz);
cout<<cnt1+cnt2<<"\n";
}
return 0;
}
5.11 bpabc438g G - Sum of Min
题意:给定两个数组 \(A, B\),求 \(\sum_{i=0}^{n-1} \min(a_i, b_{(pn+i) \bmod m})\) 的某种形式。
思路:将问题转化为对每个 \(a_i\),统计满足 \(b_j < a_i\) 的 \(b_j\) 之和以及个数,用zhuxi树维护 \(B\) 的权值线段树,查询值域前缀的和。
#include <bits/stdc++.h>
#define int long long
#define COUT cout<<"\n------------------------------------------------------------------------------------------\n";
#define fi first
#define se second
#define be begin()
#define en end()
using namespace std;
const int N=2e5+5;
const int mod=998244353;
const int inf=1e18;
int n,m;
int a[N],b[N];
int root[N],tot;
struct Node{int ls,rs,cnt,sum;}tr[N*32];
void update(int&u,int v,int l,int r,int x)
{
u=++tot;
tr[u]=tr[v];
tr[u].cnt++;
tr[u].sum+=x;
if(l==r)return;
int mid=(l+r)>>1;
if(x<=mid)update(tr[u].ls,tr[v].ls,l,mid,x);
else update(tr[u].rs,tr[v].rs,mid+1,r,x);
}
pair<int,int>query(int u,int v,int l,int r,int ql,int qr)
{
if(ql>r||qr<l)return {0,0};
if(ql<=l&&r<=qr)return {tr[u].cnt-tr[v].cnt,tr[u].sum-tr[v].sum};
int mid=(l+r)>>1;
auto L=query(tr[u].ls,tr[v].ls,l,mid,ql,qr);
auto R=query(tr[u].rs,tr[v].rs,mid+1,r,ql,qr);
return {L.first+R.first,L.second+R.second};
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=m;i++){cin>>b[i];update(root[i],root[i-1],1,N-5,b[i]);}
int g=__gcd(n,m);
int ans=0;
for(int i=1;i<=n;i++)
{
int cnt=0,sum=0;
for(int j=i;j<=m;j+=g)
{
auto res=query(root[m],root[0],1,N-5,1,a[i]-1);
cnt+=res.first;sum+=res.second;
}
ans+=cnt*a[i]+sum;
}
cout<<ans<<"\n";
return 0;
}
5.12 jzyz2420 [JSOI2018] line(军训列队)
题意:给定数列 \(a_1 \dots a_n\),每次询问将区间 \([l, r]\) 内的数重新排列到连续位置 \([k, k + r - l]\) 的最小代价。
思路:将 \(a[l..r]\) 排序后与 \(k, k+1, \dots, k+r-l\) 对应相减。在zhuxi树上二分找到分界点,使左边的数都 \(\le\) 对应位置,右边的数都 \(>\) 对应位置,然后分别计算。
#include <bits/stdc++.h>
#define int long long
#define COUT cout<<"\n------------------------------------------------------------------------------------------\n";
#define fi first
#define se second
#define be begin()
#define en end()
using namespace std;
const int N=5e5+5;
const int mod=998244353;
const int inf=1e18;
int n,m;
int a[N],root[N],tot;
struct Node{int ls,rs,cnt,sum;}tr[N*32];
void update(int&u,int v,int l,int r,int x)
{
u=++tot;
tr[u]=tr[v];
tr[u].cnt++;
tr[u].sum+=x;
if(l==r)return;
int mid=(l+r)>>1;
if(x<=mid)update(tr[u].ls,tr[v].ls,l,mid,x);
else update(tr[u].rs,tr[v].rs,mid+1,r,x);
}
int query(int u,int v,int l,int r,int ql,int qr)
{
if(ql>r||qr<l)return 0;
if(ql<=l&&r<=qr)return tr[u].sum-tr[v].sum;
int mid=(l+r)>>1;
return query(tr[u].ls,tr[v].ls,l,mid,ql,qr)+query(tr[u].rs,tr[v].rs,mid+1,r,ql,qr);
}
int querycnt(int u,int v,int l,int r,int ql,int qr)
{
if(ql>r||qr<l)return 0;
if(ql<=l&&r<=qr)return tr[u].cnt-tr[v].cnt;
int mid=(l+r)>>1;
return querycnt(tr[u].ls,tr[v].ls,l,mid,ql,qr)+querycnt(tr[u].rs,tr[v].rs,mid+1,r,ql,qr);
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++){cin>>a[i];update(root[i],root[i-1],1,N-5,a[i]);}
while(m--)
{
int l,r,k;
cin>>l>>r>>k;
int len=r-l+1;
int lo=k,hi=k+len-1;
int ans=0;
while(lo<=hi)
{
int mid=(lo+hi)>>1;
int cnt=querycnt(root[r],root[l-1],1,N-5,1,mid);
if(cnt<=mid-k+1)
{
int sum=query(root[r],root[l-1],1,N-5,1,mid);
int rest=len-cnt;
int sum2=query(root[r],root[l-1],1,N-5,mid+1,N-5);
int start2=k+cnt;
int total2=rest*start2+rest*(rest-1)/2;
ans=abs(sum-(cnt*k+cnt*(cnt-1)/2))+abs(sum2-total2);
lo=mid+1;
}
else hi=mid-1;
}
cout<<ans<<"\n";
}
return 0;
}
5.13 BZOJ4369 [IOI2015] teams分组
题意:\(N\) 个学生,每个学生愿意在规模 \([A_i, B_i]\) 的小组中。\(Q\) 次询问,每次给出 \(K\) 个小组的规模 \(k_1 \dots k_K\),判断能否分配。
思路:将学生看作二维点 \((A_i, B_i)\),每个小组 \(k\) 的需求对应矩形 \([0, k] \times [k, \infty]\)。用单调栈维护折线,zhuxi树做二维数点。
// 此题实现较复杂,核心是用zhuxi树统计矩形内点数
// 具体实现参考上述二维数点模板
// 每次询问按 k 排序后用单调栈处理合并
#include <bits/stdc++.h>
#define int long long
#define fi first
#define se second
#define be begin()
#define en end()
using namespace std;
const int L=10000005,M=200005;
int n,N,cnt,st[M],K[M],rt[3*M],h[M],sz[M];
struct Tr{int num,ls,rs;}t[L];
struct node{int x,y;}e[L];
bool cmp(node a,node b){return a.x<b.x;}
inline int ms(int l,int r){return (r+l)>>1;}
inline void insert(int pre,int &id,int l,int r,int ID)
{
id=++cnt;
t[id].num=t[pre].num+1;
t[id].ls=t[pre].ls;
t[id].rs=t[pre].rs;
if(l==r) return ;
int mid=ms(l,r);
if(ID<=mid) insert(t[pre].ls,t[id].ls,l,mid,ID);
else insert(t[pre].rs,t[id].rs,mid+1,r,ID);
}
inline int query_K(int pre,int id,int l,int r,int k)
{
if(l==r) return l;
int mid=ms(l,r);
int ans=t[t[id].rs].num-t[t[pre].rs].num;
if(ans>=k) return query_K(t[pre].rs,t[id].rs,mid+1,r,k);
else return query_K(t[pre].ls,t[id].ls,l,mid,k-ans);
}
inline int query(int pre,int id,int l,int r,int k)
{
if(!id) return 0;
if(l==r) return t[id].num-t[pre].num;
int mid=ms(l,r);
if(k<=mid) return t[t[id].rs].num-t[t[pre].rs].num+query(t[pre].ls,t[id].ls,l,mid,k);
else return query(t[pre].rs,t[id].rs,mid+1,r,k);
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++) cin>>e[i].x>>e[i].y;
sort(e+1,e+n+1,cmp);
N=n+1;
int head=1;
for(int i=1;i<=N;i++)
{
rt[i]=rt[i-1];
while(head<=n&&e[head].x==i) insert(rt[i],rt[i],1,N,e[head++].y);
}
int Q;
cin>>Q;
while(Q--)
{
int m;
cin>>m;
for(int i=1;i<=m;i++) cin>>K[i];
sort(K+1,K+m+1);
int top=0;
for(int i=1;i<=m;i++)
{
while(top&&h[top]<K[i]) top--;
int Sz=sz[top];
Sz+=query(rt[K[st[top]]],rt[K[i]],1,N,K[i])-K[i];
if(Sz<0)
{
cout<<"0\n";
break;
}
else if(i==m)
{
cout<<"1\n";
break;
}
int H=query_K(rt[K[st[top]]],rt[K[i]],1,N,Sz-sz[top]);
while(H>h[top]&&top)
{
top--;
H=query_K(rt[K[st[top]]],rt[K[i]],1,N,Sz-sz[top]);
}
st[++top]=i;
sz[top]=Sz;
h[top]=H;
}
}
return 0;
}
5.14 cses2184 Missing Coin Sum Queries
题意:查询区间 \([l, r]\) 的最小不可表示和。
思路:经典贪心:维护当前可表示范围 \([1, x]\),每次找 \(\le x+1\) 的最小数加入。用zhuxi树在值域上查询区间内 \(\le x+1\) 的数的和,不断扩展直到无法扩展。
#include <bits/stdc++.h>
#define int long long
#define COUT cout<<"\n------------------------------------------------------------------------------------------\n";
#define fi first
#define se second
#define be begin()
#define en end()
using namespace std;
const int N=2e5+5;
const int mod=998244353;
const int inf=1e18;
int n,m;
int a[N],root[N],tot;
struct Node{int ls,rs,sum;}tr[N*32];
void update(int&u,int v,int l,int r,int x)
{
u=++tot;
tr[u]=tr[v];
tr[u].sum+=x;
if(l==r)return;
int mid=(l+r)>>1;
if(x<=mid)update(tr[u].ls,tr[v].ls,l,mid,x);
else update(tr[u].rs,tr[v].rs,mid+1,r,x);
}
int query(int u,int v,int l,int r,int ql,int qr)
{
if(ql>r||qr<l)return 0;
if(ql<=l&&r<=qr)return tr[u].sum-tr[v].sum;
int mid=(l+r)>>1;
return query(tr[u].ls,tr[v].ls,l,mid,ql,qr)+query(tr[u].rs,tr[v].rs,mid+1,r,ql,qr);
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++){cin>>a[i];update(root[i],root[i-1],1,N-5,a[i]);}
while(m--)
{
int l,r;cin>>l>>r;
int x=0;
while(true)
{
int s=query(root[r],root[l-1],1,N-5,1,min(x+1,N-5LL));
if(s<=x)break;
x=s;
}
cout<<x+1<<"\n";
}
return 0;
}
第六章 高级话题与研究扩展
6.1 zhuxi树与整体二分
静态区间第 \(k\) 小也可以用整体二分解决,时间复杂度 \(O((n + q) \log n \log V)\),空间复杂度 \(O(n + q)\),优于zhuxi树的 \(O(n \log n)\) 空间。但整体二分是离线的,zhuxi树支持强制在线。
6.2 树套树的其他形式
除了树状数组套zhuxi树,还有线段树套平衡树、树状数组套Trie等。zhuxi树套树状数组适合解决带修区间第 \(k\) 小,而树状数组套Trie适合解决带修区间最大异或和。
6.3 可持久化平衡树
可持久化Treap(无旋Treap)也可实现可持久化,且常数通常优于zhuxi树。无旋Treap的Split和Merge操作天然支持路径复制,实现可持久化非常自然。
6.4 zhuxi树在二维问题中的应用
zhuxi树可以解决许多二维数点问题:
- 矩形内点数查询
- 矩形内权值和查询
- 区间内值在 \([L, R]\) 的元素个数
核心思想是以下标为版本,权值为线段树下标,利用前缀和差分。
6.5 zhuxi树的常数优化技巧
- 离散化:减少值域大小,降低树高。
- 节点回收:带修zhuxi树中,被覆盖的节点可以回收利用。
- 数组开大:通常开 \(n \times 32\) 或 \(n \times 40\) 避免RE。
- 快读快写:对于 \(2 \times 10^5\) 级别的数据,使用快读能显著提速。
- 递归改迭代:查询操作可以改为非递归,减少函数调用开销。
6.6 zhuxi树的典型错误与调试
错误1:数组越界。zhuxi树节点数估算不足。对于 \(n = 2 \times 10^5\),插入操作产生 \(n \log n \approx 3.6 \times 10^6\) 个节点,建议开 \(n \times 32\)。
错误2:离散化后忘记用 \(b\) 数组映射回原值。查询返回的是离散化后的下标,输出时需要 b[ans]。
错误3:树上zhuxi树中LCA查询错误。注意 up[lc][0] 是 \(lc\) 的父节点,如果 \(lc\) 是根节点,则 up[lc][0] = 0,需要保证 root[0] 是一棵空树。
错误4:带修zhuxi树中 tmp1 和 tmp2 数组大小。树状数组每次查询会涉及 \(O(\log n)\) 棵线段树,数组大小要开到 \(O(\log n)\) 以上。
第七章 总结与学习路线
7.1 学习路线
- 入门:静态区间第 \(k\) 小(P3834)
- 进阶:可持久化数组(P3919)
- 树上应用:树上路径第 \(k\) 小(P2633 / BZOJ2588)
- 带修改:动态区间第 \(k\) 小(P2617)
- 可持久化Trie:最大异或和(P4735)
- 复杂应用:二维数点、区间合并(CF484E)
- 高级:可持久化并查集(P3402)
7.2 核心公式速查
| 场景 | 公式 |
|---|---|
| 静态区间第k小 | \(cnt = sum[ls_r] - sum[ls_{l-1}]\) |
| 树上路径第k小 | \(cnt = sum[ls_u] + sum[ls_v] - sum[ls_{lca}] - sum[ls_{fa[lca]}]\) |
| 带修zhuxi树 | \(cnt = \sum sum[ls_{tmp1}] - \sum sum[ls_{tmp2}]\) |
| 区间合并lmx | \(lmx = lmx_L + (mx_L == len_L ? lmx_R : 0)\) |
| 区间合并mx | \(mx = \max(mx_L, mx_R, rmx_L + lmx_R)\) |
7.3 复杂度总表
| 操作 | 静态zhuxi树 | 带修zhuxi树 | 树上zhuxi树 |
|---|---|---|---|
| 建树 | \(O(n \log n)\) | \(O(n \log^2 n)\) | \(O(n \log n)\) |
| 修改 | 不支持 | \(O(\log^2 n)\) | 不支持 |
| 查询 | \(O(\log n)\) | \(O(\log^2 n)\) | \(O(\log n)\) |
| 空间 | \(O(n \log n)\) | \(O(n \log^2 n)\) | \(O(n \log n)\) |
zhuxi树是可持久化数据结构中最重要、最常用的成员之一。掌握它不仅能解决区间第 \(k\) 小问题,还能推广到树上路径、二维数点、带修改查询等多种场景。建议从模板题开始,逐步深入,理解“路径复制”和“前缀和差分”这两个核心思想,就能举一反三,应对各种变形。

浙公网安备 33010602011771号