主席树(可持续化线段树)板子

本文详细介绍了可持久化线段树(主席树)的概念及其实现方式,通过实例展示了如何使用主席树解决数组中查找第k大元素的问题。此外,还提供了一道关于两个排列共同元素查询的题目,进一步解释了主席树在处理在线查询中的高效性。文章深入浅出地阐述了数据结构在解决复杂问题中的重要作用。
摘要由CSDN通过智能技术生成

主席树是什么

一般来说,主席树是n个权值线段树组合成的一棵杂树。其中n是数组大小,举个例子:

数组 [2,3,1,4],由它构成的主席树就是[], [2], [2,3], [2,3,1], [2,3,1,4]对应的五棵权值线段树一起建出来的一棵杂树。下面讲几个重点问题:

  1. 如何理解权值线段树:我的理解是,给一个桶建树,就是权值线段树。不过要注意这个”桶“常常需要离散化。
  2. 主席树如何实现省空间和省时间:主席树中,只有空数组对应的0号树是完整的,后面的树都是在前面树的基础上加一条链组成树,重复利用了之前的信息。

主席树板子

板子题:【模板】可持久化线段树 2 - 洛谷 

#include<bits/stdc++.h>
using namespace std;
#define FOR(i, a, b) for (int i = (a); i <= (b); i++)
#define ROF(i, a, b) for (int i = (a); i >= (b); i--) 
#define int long long
const int N = 2e5+10;
int n,m,a[N];
vector<int> v;
int rt[N],tot=0;
struct PT{
    int ls,rs,num; //左儿子,右儿子,数字数量
}t[N<<6];

int find(int x){return lower_bound(v.begin(),v.end(),x)-v.begin()+1;} //根据val找pos
void ins(int pre,int&u,int l,int r,int pos){
	u=++tot; //每次insert都要新建结点
	t[u]=t[pre]; //u继承pre的信息
	t[u].num++; //数字数量+1
	if(l==r) return;
	int mid=l+r>>1; //二分区间,准备向下处理两个儿子
	if(pos<=mid) ins(t[pre].ls, t[u].ls, l, mid, pos);
	else ins(t[pre].rs, t[u].rs, mid+1, r, pos);
}
int query(int L,int R, int l,int r,int k){
	if(l>=r) return l; //区间长度只剩1,说明找到了
	int mid=l+r>>1, Ldiff=t[t[R].ls].num-t[t[L].ls].num; //中间位置,左儿子数字数量差
	if(Ldiff>=k) return query(t[L].ls, t[R].ls, l, mid, k);
	else return query(t[L].rs, t[R].rs, mid+1, r, k-Ldiff);
}
inline void solve(){
	cin>>n>>m;
	FOR(i,1,n) cin>>a[i], v.push_back(a[i]);
	sort(v.begin(), v.end()); v.erase(unique(v.begin(),v.end()),v.end()); //排序+去重
	int D=v.size(); //a数组中只出现了D种数字

	FOR(i,1,n){
		rt[i]=rt[i-1]; //继承之前的信息
		int pos = find(a[i]);
		ins(rt[i], rt[i], 1, D, pos); //让pos位置+1
	}
	FOR(i,1,m){
		int l,r,k; cin>>l>>r>>k; //输入[l,r],询问其中第k大
		cout<<v[query(rt[l-1], rt[r], 1, D, k)-1]<<'\n'; //输出结果(注意find中加了1,这里对应位置要-1)
	}
}
signed main(){
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
	int T=1; //cin>>T;
	while(T--) solve();
}

以下是练习题:

1.Two permutations - 洛谷

题意翻译

  • 给定两个长度均为 n 的 排列
  • m 次询问。每次询问您要求出在第一个排列的 [l1, r1] 和第二个排列的 [l2, r2] 同时出现的数有多少个。
  • 1<=n<=1e6,1<=m<=2e5。强制在线。(原题中有写到转换关系,这里要看原题)

代码: 

#include<bits/stdc++.h>
using namespace std;
#define FOR(i, a, b) for (int i = (a); i <= (b); i++)
#define ROF(i, a, b) for (int i = (a); i >= (b); i--) 
// #define int long long
const int N = 1e6+10;

int n,m,pos[N];
int rt[N],tot=0;
int ans=0;
struct PT{
    int ls,rs,num; //左儿子,右儿子,数字数量
}t[N*40];

int rd(){
    int x = 1;
    char ch;
    while (ch = getchar(),ch < '0' || ch > '9') 
		if (ch == '-') 
			x = -1;
    int s = ch-'0';
    while (ch = getchar(),ch >= '0' && ch <= '9') 
		s = s * 10 + ch - '0';
    return s * x;
}
int f(int x){return (x-1+ans)%n+1;}
void ins(int pre,int&u,int l,int r,int pos){
	u=++tot; //每次insert都要新建结点
	t[u]=t[pre]; //u继承pre的信息
	t[u].num++; //数字数量+1
	if(l==r) return;
	int mid=l+r>>1; //二分区间,准备向下处理两个儿子
	if(pos<=mid) ins(t[pre].ls, t[u].ls, l, mid, pos);
	else ins(t[pre].rs, t[u].rs, mid+1, r, pos);
}
int query(int o,int l,int r,int x,int y){
	if(x<=l && r<=y) return t[o].num;
	int mid=l+r>>1, res=0;
	if(x<=mid) res+=query(t[o].ls,l,mid,x,y);
	if(y >mid) res+=query(t[o].rs,mid+1,r,x,y);
	return res;
}
signed main(){
	cin>>n;
	FOR(i,1,n) pos[rd()]=i;
	FOR(i,1,n) ins(rt[i-1],rt[i],1,n,pos[rd()]);
	cin>>m;
	while(m--){
		int w=f(rd()), x=f(rd()), y=f(rd()), z=f(rd());
		if(w>x) swap(w,x);
		if(y>z) swap(y,z);
		ans = query(rt[z],1,n,w,x)-query(rt[y-1],1,n,w,x);
		printf("%d\n",ans);
		ans++;
	}
	return 0;
}

 

在C++中,可持续(Lazy Propagation)也称为延迟更新(Lazy Evaluation),通常与线段树(Segment Tree)结合使用,这是一种高效的数据结构,用于支持区间查询和修改操作。线段树常用于解决范围查询问题,如计算区间的和、积、最大值、最小值等问题。 **线段树(Segment Tree)**是一种自底向上的数据结构,通过分治思想将原始数组分解成若干个连续的子区间,并为每个区间维护一个总结算结果。它能够快速地对区间进行查询和修改操作。 **可持续/延迟更新(Lazy Propagation)**的概念允许在线段树上执行某些昂贵的操作(比如计算平方或更新某个元素)时只影响到需要的部分,而不是立即应用到整个树。这样可以避免不必要的重复计算,提高效率。具体来说,在线段树中,当遇到需要更新操作时,不会立刻改变该节点的结果,而是将其标记为待更新,然后在之后的访问过程中再进行更新。 下面是一个简单的线段树示例(不涉及懒惰更新)[^4]: ```cpp struct Node { int val; int lazy; // 延迟更新标记 // ... }; void updateRange(Node &node, int start, int end, int new_val) { node.val = ...; // 更新操作 if (start != end) { // 如果不是叶子节点 node.lazy = ...; // 分配给子节点 } } ``` 要实现懒惰更新,我们需要在`updateRange`和`query`函数中加入逻辑来处理`lazy`标志。相关问题如下: 1. 如何在C++线段树中实现懒惰更新? 2. 懒惰更新主要用于优哪种类型的查询或修改操作? 3. 是否有其他术语描述这种结合线段树和懒惰更新的数据结构
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值