可持久化线段树
前置
线段树
引入
可持久化线段树模板题:P3834 【模板】可持久化线段树 2
题目大意:给定由
思路:每一次加入新的数都建一个线段树,保存所有历史版本,以便查询区间第
解释
思路
整体思路就是逐一插入每一个数字,然后利用前缀和的思想求解。
由于
我们通过样例来模拟:
5 5 25957 6405 15770 26287 26465 2 2 1 3 4 1 4 5 1 1 2 2 4 4 1
离散化后的结果:
〇 首先建一棵空树:
然后依次将每个离散化后的数字的编号插入到它的位置上,然后把所有包括它的区间的
① 加入
假设要查询
然后我们把对应节点的数相减,刚好就是
然后对于每一个区间
由此模拟可得,
但是呢,我们不可能每到一个版本就建一颗完整的线段树,这样的话空间会爆炸,所以我们动态开点,每次建线段树只修改我们需要修改的节点,其他的节点我们仍然使用上一个版本的线段树的节点。举个例子:
假设我们现在是从第①棵线段树到第②棵线段树,我们发现我们只有蓝色的节点改变了数值,所以我们只添加这些改变数值的节点,其他节点仍然使用第①棵线段树的节点。
由此可以得出,我们不能单纯用
代码实现
我们需要用动态开点的方法来储存每个节点的左右儿子编号,同时需要记录每一个历史版本的根节点。
离散化
struct A{int w,id;}a[N]; bool cmp(A a,A b) {return a.w < b.w;} void Input(){ scanf("%d%d",&n,&m); for(int i = 1;i <= n;i ++) scanf("%d",&a[i].w),a[i].id = i; sort(a + 1,a + n + 1,cmp); for(int i = 1;i <= n;i ++) _rank[a[i].id] = i,sum[i] = a[i].w; }
建空树
void build(int &p,int l,int r){ p = ++tmp; if(l == r) return ; int mid = l + r >> 1; build(t[p].ls,l,mid); build(t[p].rs,mid + 1,r); } Set.build(rt[0],1,n);
加点
记录每一个线段树的根节点。
加点前我们先把历史版本复制过来,然后再进行修改,因为修改只会涉及其左子树或者其右子树。
通过查找当前加入得节点的位置,对其进行对应权值的修改。
void new_copy(int &p) {t[++tmp] = t[p],p = tmp;} void update(int &p,int l,int r,int x){ new_copy(p),t[p].ans ++; if(l == r) return ; int mid = l + r >> 1; if(x <= mid) update(t[p].ls,l,mid,x); else update(t[p].rs,mid + 1,r,x); } for(int i = 1;i <= n;i ++) rt[i] = rt[i - 1],Set.update(rt[i],1,n,_rank[i]);
查询
利用前缀和的思想,当前节点的权值
int query(int a,int b,int l,int r,int x){ if(l == r) return l; int l_sum = t[t[b].ls].ans - t[t[a].ls].ans; int mid = l + r >> 1; if(x <= l_sum) return query(t[a].ls,t[b].ls,l,mid,x); else return query(t[a].rs,t[b].rs,mid + 1,r,x - l_sum); } printf("%d\n",sum[Set.query(rt[l - 1],rt[r],1,n,x)]);
不得贪胜,不可不胜。
#include <bits/stdc++.h> #define N 200005 using namespace std; int n,m,_rank[N],sum[N],tmp,rt[N]; struct A{int w,id;}a[N]; bool cmp(A a,A b) {return a.w < b.w;} void Input(){ scanf("%d%d",&n,&m); for(int i = 1;i <= n;i ++) scanf("%d",&a[i].w),a[i].id = i; sort(a + 1,a + n + 1,cmp); for(int i = 1;i <= n;i ++) _rank[a[i].id] = i,sum[i] = a[i].w; } struct Set{ struct Tree{int ls,rs,ans;}t[N << 5]; void build(int &p,int l,int r){ p = ++tmp; if(l == r) return ; int mid = l + r >> 1; build(t[p].ls,l,mid); build(t[p].rs,mid + 1,r); } void new_copy(int &p) {t[++tmp] = t[p],p = tmp;} void update(int &p,int l,int r,int x){ new_copy(p),t[p].ans ++; if(l == r) return ; int mid = l + r >> 1; if(x <= mid) update(t[p].ls,l,mid,x); else update(t[p].rs,mid + 1,r,x); } int query(int a,int b,int l,int r,int x){ if(l == r) return l; int l_sum = t[t[b].ls].ans - t[t[a].ls].ans; int mid = l + r >> 1; if(x <= l_sum) return query(t[a].ls,t[b].ls,l,mid,x); else return query(t[a].rs,t[b].rs,mid + 1,r,x - l_sum); } }Set; void work(){ Set.build(rt[0],1,n); for(int i = 1;i <= n;i ++) rt[i] = rt[i - 1],Set.update(rt[i],1,n,_rank[i]); int l,r,x;while(m --){ scanf("%d%d%d",&l,&r,&x); printf("%d\n",sum[Set.query(rt[l - 1],rt[r],1,n,x)]); } } int main(){ Input(); work(); return 0; }
三倍经验: SP3946 MKTHNUM - K-th Number(代码完全一样)P1533 可怜的狗狗(改一下数组大小就行)。
习题
P3919 【模板】可持久化线段树 1(可持久化数组)
线段树节点维护对应数组的值,然后利用主席树思想修改、查询即可。
若世有神明,亦会胜他半子。
#include <bits/stdc++.h> #define N 1000006 using namespace std; int n,m,a[N],rt[N]; void Input(){ scanf("%d%d",&n,&m); for(int i = 1;i <= n;i ++) scanf("%d",&a[i]); } struct Set_Tree{ struct Tree{int ls,rs,ans;}t[N << 5]; int tmp; void build(int &p,int l,int r){ p = ++tmp; if(l == r) return (void)(t[p].ans = a[l]); int mid = l + r >> 1; build(t[p].ls,l,mid); build(t[p].rs,mid + 1,r); } void new_copy(int &p) {t[++tmp] = t[p],p = tmp;} void update(int &p,int l,int r,int loc,int k){ new_copy(p); if(l == r) return (void)(t[p].ans = k); int mid = l + r >> 1; if(loc <= mid) update(t[p].ls,l,mid,loc,k); else update(t[p].rs,mid + 1,r,loc,k); } int query(int p,int l,int r,int loc){ if(l == r) return t[p].ans; int mid = l + r >> 1; if(loc <= mid) return query(t[p].ls,l,mid,loc); else return query(t[p].rs,mid + 1,r,loc); } }Set; void work(){ Set.build(rt[0],1,n); int v,opt,loc,x;for(int i = 1;i <= m;i ++){ scanf("%d%d%d",&v,&opt,&loc); rt[i] = rt[v]; if(opt == 1) scanf("%d",&x),Set.update(rt[i],1,n,loc,x); if(opt == 2) printf("%d\n",Set.query(rt[v],1,n,loc)); } } int main(){ Input(); work(); return 0; }
P3567 [POI2014] KUR-Couriers
思路:对于序列
方寸棋盘,便是我的天地。
#include <bits/stdc++.h> #define N 500005 using namespace std; int n,m,a[N],rt[N]; void Input(){ scanf("%d%d",&n,&m); for(int i = 1;i <= n;i ++) scanf("%d",&a[i]); } struct Set_Tree{ struct Tree{int ls,rs,ans;}t[N << 5]; int tmp; void build(int &p,int l,int r){ p = ++tmp;t[p].ans = 0; if(l == r) return ; int mid = l + r >> 1; build(t[p].ls,l,mid); build(t[p].rs,mid + 1,r); } void new_copy(int &p) {t[++tmp] = t[p],p = tmp;} void update(int &p,int l,int r,int x){ new_copy(p);t[p].ans ++; if(l == r) return ; int mid = l + r >> 1; if(x <= mid) update(t[p].ls,l,mid,x); else update(t[p].rs,mid + 1,r,x); } int query(int a,int b,int l,int r,int x){ if(l == r) return l; int l_sum = t[t[a].ls].ans - t[t[b].ls].ans; int r_sum = t[t[a].rs].ans - t[t[b].rs].ans; int mid = l + r >> 1; if(l_sum * 2 > x) return query(t[a].ls,t[b].ls,l,mid,x); if(r_sum * 2 > x) return query(t[a].rs,t[b].rs,mid + 1,r,x); return 0; } }Set; void work(){ Set.build(rt[0],1,n); for(int i = 1;i <= n;i ++) rt[i] = rt[i - 1],Set.update(rt[i],1,n,a[i]); int l,r;while(m --){ scanf("%d%d",&l,&r); printf("%d\n",Set.query(rt[r],rt[l - 1],1,n,r - l + 1)); } } int main(){ Input(); work(); return 0; }
P1972 [SDOI2009] HH的项链
每一个线段树维护的是从
双倍经验: SP3267 DQUERY - D-query(代码完全一样)。
黑子深邃,为长夜苍茫莫测;白子耀眼,若恒星亘古不变。
#include <bits/stdc++.h> #define N 1000006 using namespace std; int n,m,a[N],rt[N],head[N]; void Input(){ scanf("%d",&n); for(int i = 1;i <= n;i ++) scanf("%d",&a[i]); } struct Set_Tree{ struct Tree{int ls,rs,ans;}t[N * 40]; int tmp; void build(int &p,int l,int r){ p = ++tmp; if(l == r) return ; int mid = l + r >> 1; build(t[p].ls,l,mid); build(t[p].rs,mid + 1,r); } void new_copy(int &p) {t[++tmp] = t[p],p = tmp;} void update(int &p,int l,int r,int loc,int x){ new_copy(p);t[p].ans += x; if(l == r) return ; int mid = l + r >> 1; if(loc <= mid) update(t[p].ls,l,mid,loc,x); else update(t[p].rs,mid + 1,r,loc,x); } int query(int p,int l,int r,int x){ if(l == r) return t[p].ans; int mid = l + r >> 1; if(x <= mid) return query(t[p].ls,l,mid,x) + t[t[p].rs].ans; else return query(t[p].rs,mid + 1,r,x); } }Set; void work(){ Set.build(rt[0],1,n); for(int i = 1;i <= n;i ++){ if(!head[a[i]]) rt[i] = rt[i - 1],Set.update(rt[i],1,n,i,1); else rt[i] = rt[i - 1],Set.update(rt[i],1,n,head[a[i]],-1),Set.update(rt[i],1,n,i,1); head[a[i]] = i; } int l,r;scanf("%d",&m);while(m --){ scanf("%d%d",&l,&r); printf("%d\n",Set.query(rt[r],1,n,l)); } } int main(){ Input(); work(); return 0; }
P3939 数颜色
对于每一个颜色建一颗线段树,修改、查询即可。
会一直胜下去,为了父亲大人的认可。
#include <bits/stdc++.h> #define N 300005 using namespace std; struct Set_Tree{ struct Tree{int ls,rs,ans;}t[N * 40]; int tmp; void update(int &p,int l,int r,int x,int k){ if(!p) p = ++tmp;t[p].ans += k; if(l == r) return ; int mid = l + r >> 1; if(x <= mid) update(t[p].ls,l,mid,x,k); else update(t[p].rs,mid + 1,r,x,k); } int query(int p,int l,int r,int nl,int nr){ if(nl <= l and r <= nr) return t[p].ans; int mid = l + r >> 1,res = 0; if(nl <= mid) res += query(t[p].ls,l,mid,nl,nr); if(nr > mid) res += query(t[p].rs,mid + 1,r,nl,nr); return res; } }Set; int n,m,a[N],rt[N]; void Input(){ scanf("%d%d",&n,&m); for(int i = 1;i <= n;i ++) scanf("%d",&a[i]),Set.update(rt[a[i]],1,n,i,1); } void work(){ int opt,l,r,c;while(m --){ scanf("%d",&opt); if(opt == 1) scanf("%d%d%d",&l,&r,&c),printf("%d\n",Set.query(rt[c],1,n,l,r)); if(opt == 2) scanf("%d",&c),Set.update(rt[a[c]],1,n,c,-1),Set.update(rt[a[c]],1,n,c + 1,1),Set.update(rt[a[c + 1]],1,n,c + 1,-1),Set.update(rt[a[c + 1]],1,n,c,1),swap(a[c],a[c + 1]); } } int main(){ Input(); work(); return 0; }
P2633 Count on a tree
前置知识:树链剖分。
这个题与模板不同的是,这个题的操作是在树上进行的。整体思路不变,只不过修改的时候是按照在熟练剖分预处理
落子无悔。
#include <bits/stdc++.h> #define N 100005 using namespace std; struct A{int w,id;}a[N]; bool cmp(A a,A b) {return a.w < b.w;} struct Edge{int next,to;}edge[N << 1]; int head[N],cnt; void add(int from,int to){ edge[++cnt] = (Edge){head[from],to}; head[from] = cnt; } int n,m,_rank[N],sum[N],rt[N],siz[N],dep[N],fa[N],top[N],son[N]; void Input(){ scanf("%d%d",&n,&m); for(int i = 1;i <= n;i ++) scanf("%d",&a[i].w),a[i].id = i; sort(a + 1,a + n + 1,cmp); for(int i = 1;i <= n;i ++) _rank[a[i].id] = i,sum[i] = a[i].w; for(int i = 1,u,v;i < n;i ++) scanf("%d%d",&u,&v),add(u,v),add(v,u); } struct Set_Tree{ struct Set{int ls,rs,ans;}t[N * 40]; int tmp; void build(int &p,int l,int r){ p = ++tmp;t[p].ans = 0; if(l == r) return ; int mid = l + r >> 1; build(t[p].ls,l,mid); build(t[p].rs,mid + 1,r); } void new_copy(int &p) {t[++tmp] = t[p],p = tmp;} void update(int &p,int l,int r,int x){ new_copy(p); if(l == r) return (void)(t[p].ans ++); int mid = l + r >> 1; if(x <= mid) update(t[p].ls,l,mid,x); else update(t[p].rs,mid + 1,r,x); t[p].ans = t[t[p].ls].ans + t[t[p].rs].ans; } int query(int x,int y,int w,int z,int l,int r,int k){ if(l == r) return sum[l]; int l_sum = t[t[x].ls].ans + t[t[y].ls].ans - t[t[w].ls].ans - t[t[z].ls].ans; int mid = l + r >> 1; if(k <= l_sum) return query(t[x].ls,t[y].ls,t[w].ls,t[z].ls,l,mid,k); else return query(t[x].rs,t[y].rs,t[w].rs,t[z].rs,mid + 1,r,k - l_sum); } }Set; struct Tree_apart{ void dfs1(int x,int f,int deep){ siz[x] = 1,dep[x] = deep,fa[x] = f; int maxnson = -1; for(int i = head[x];i;i = edge[i].next){ int y = edge[i].to; if(y == f) continue; dfs1(y,x,deep + 1); siz[x] += siz[y]; if(siz[y] > maxnson) maxnson = siz[y],son[x] = y; } } void dfs2(int x,int t){ rt[x] = rt[fa[x]],Set.update(rt[x],1,n,_rank[x]); top[x] = t; if(!son[x]) return ; dfs2(son[x],t); for(int i = head[x];i;i = edge[i].next){ int y = edge[i].to; if(y == son[x] or y == fa[x]) continue; dfs2(y,y); } } int LCA(int x,int y){ while(top[x] != top[y]){ if(dep[top[x]] < dep[top[y]]) swap(x,y); x = fa[top[x]]; } return dep[x] < dep[y] ? x : y; } }Ta; void work(){ Ta.dfs1(1,0,1),Set.build(rt[0],1,n),Ta.dfs2(1,1); int u,v,k,lst = 0;while(m --){ scanf("%d%d%d",&u,&v,&k);u ^= lst; int l = Ta.LCA(u,v); lst = Set.query(rt[u],rt[v],rt[l],rt[fa[l]],1,n,k); printf("%d\n",lst); } } int main(){ Input(); work(); return 0; }
P1383 高级打字机
每一个线段树就维护每个节点是哪一个字母就行。对于第一个操作,就在第一个还没有插入字母的位置插入即可,具体实现方法就是,维护一个
点击查看代码
#include <bits/stdc++.h> #define N 100005 using namespace std; struct Set_Tree{ struct Tree{int ls,rs,ans,sum;}t[N << 5]; int tmp; void build(int &p,int l,int r){ p = ++tmp;t[p].sum = 0; if(l == r) return ; int mid = l + r >> 1; build(t[p].ls,l,mid); build(t[p].rs,mid + 1,r); } void new_copy(int &p) {t[++tmp] = t[p],p = tmp;} void update(int &p,int l,int r,char x){ new_copy(p);t[p].sum ++; if(l == r) return (void)(t[p].ans = x); int mid = l + r >> 1; if(t[t[p].ls].sum < mid - l + 1) update(t[p].ls,l,mid,x); else update(t[p].rs,mid + 1,r,x); } char query(int p,int l,int r,int k){ if(l == r) return t[p].ans; int mid = l + r >> 1; if(t[t[p].ls].sum >= k) return query(t[p].ls,l,mid,k); else return query(t[p].rs,mid + 1,r,k - t[t[p].ls].sum); } }Set; int n,rt[N]; int main(){ scanf("%d",&n);Set.build(rt[0],1,n); char opt,c;int x,ans = 0;for(int i = 1;i <= n;i ++){ cin >> opt; if(opt == 'T') cin >> c,ans ++,rt[ans] = rt[ans - 1],Set.update(rt[ans],1,n,c); if(opt == 'U') scanf("%d",&x),ans ++,rt[ans] = rt[ans - x - 1]; if(opt == 'Q') scanf("%d",&x),cout << Set.query(rt[ans],1,n,x) << endl; } return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具