可持久化线段树

可持久化线段树

可持久化线段树就是对[1,n]的每一个i,都建一颗线段树,然后为了省空间就复制了一部分的树

#include<bits/stdc++.h>
using namespace std;

const int N=2e5+5;
struct zxs{
	int l,r,sum,ed;
}q[N*18*4];
int n,m,a[N],b[N],c[N],cnt=0;
int rt[N];
int newnode(int id){
	q[++cnt]=q[id];
	return cnt;
}
int uppdate(int l,int r,int id,int x,int val){
	int idd=newnode(id);
	if(l==r){
		q[idd].ed=x;
		q[idd].sum++;
		return idd;
	}
	int mid=(l+r)>>1;
	if(val<=mid) q[idd].l=uppdate(l,mid,q[id].l,x,val);//
	else q[idd].r=uppdate(mid+1,r,q[id].r,x,val);//
	int ls=q[q[idd].l].sum,rs=q[q[idd].r].sum;
	q[idd].sum=ls+rs;
	return idd;
}
int query(int l,int r,int u,int v,int k){
	if(l==r){
		return q[v].ed;
	}
	int mid=(l+r)>>1;
	int vv=q[v].l,uu=q[u].l;
	int stmp=q[vv].sum-q[uu].sum;
	if(k<=stmp) return query(l,mid,q[u].l,q[v].l,k);
	else return query(mid+1,r,q[u].r,q[v].r,k-stmp);
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
	    b[i]=a[i];
	}
	sort(b+1,b+n+1);
	int nn=unique(b+1,b+n+1)-b-1;
	for(int i=1;i<=n;i++){
		c[i]=upper_bound(b+1,b+nn+1,a[i])-b-1;
	};
    for(int i=1;i<=n;i++)
    {
    	rt[i]=uppdate(1,nn,rt[i-1],i,c[i]);
    }
    for(int i=1;i<=m;i++){
    	int l,r,k;
    	scanf("%d%d%d",&l,&r,&k);
    	int tp=query(1,nn,rt[l-1],rt[r],k);
    	printf("%d\n",a[tp]);
    }
 }
可持久化线段树是一种支持历史版本查询的数据结构,其核心思想是在每次修改操作时保留完整的旧版本信息。这使得它在某些应用场景中非常有用,例如版本控制系统或需要回溯操作的算法问题。 ### 空间复杂度分析 可持久化线段树的空间复杂度与普通线段树相比有所增加。普通线段树的空间复杂度为 $O(n)$,其中 $n$ 是数据规模。而可持久化线段树由于需要保留历史版本,每次更新操作都会生成新的节点,因此其空间复杂度为 $O(n \log n)$。具体来说,每次更新操作最多会生成 $O(\log n)$ 个新节点,因为线段树的高度为 $O(\log n)$,每个节点最多分裂一次[^1]。 ### 实现原理 可持久化线段树的核心实现原理是**节点复用**和**路径复制**。当对线段树进行更新时,只有从根节点到目标节点的路径上的节点会被复制,其余节点保持不变。这种方式避免了对整个线段树的完全复制,从而节省了内存[^1]。 具体实现中,每个版本的线段树通过一个根节点指针来标识。当进行更新操作时,新版本的根节点指向一个新的节点,而未修改的子则继续指向旧版本的节点。这种设计使得不同版本之间可以共享未修改的部分,从而减少内存开销。 以下是一个简单的可持久化线段树的实现示例,用于单点更新和区间查询: ```cpp #include <iostream> #include <vector> using namespace std; struct Node { int val; // 节点值,例如区间和 Node* left; Node* right; Node(int v) : val(v), left(nullptr), right(nullptr) {} }; class PersistentSegmentTree { private: vector<int> data; Node* build(Node* node, int l, int r) { if (l == r) { node->val = data[l]; return node; } int mid = (l + r) / 2; node->left = new Node(0); node->right = new Node(0); build(node->left, l, mid); build(node->right, mid + 1, r); node->val = node->left->val + node->right->val; return node; } Node* update(Node* node, int l, int r, int idx, int value) { if (l == r) { Node* new_node = new Node(value); return new_node; } int mid = (l + r) / 2; Node* new_node = new Node(0); if (idx <= mid) { new_node->left = update(node->left, l, mid, idx, value); new_node->right = node->right; } else { new_node->left = node->left; new_node->right = update(node->right, mid + 1, r, idx, value); } new_node->val = new_node->left->val + new_node->right->val; return new_node; } int query(Node* node, int l, int r, int ql, int qr) { if (qr < l || ql > r) return 0; if (ql <= l && r <= qr) return node->val; int mid = (l + r) / 2; return query(node->left, l, mid, ql, qr) + query(node->right, mid + 1, r, ql, qr); } public: vector<Node*> roots; // 存储每个版本的根节点 PersistentSegmentTree(vector<int>& arr) { data = arr; roots.push_back(new Node(0)); build(roots[0], 0, data.size() - 1); } void update(int version, int idx, int value) { Node* new_root = update(roots[version], 0, data.size() - 1, idx, value); roots.push_back(new_root); } int query(int version, int ql, int qr) { return query(roots[version], 0, data.size() - 1, ql, qr); } }; ``` ### 内存占用分析 可持久化线段树的内存占用主要由以下几个部分构成: 1. **节点存储**:每个节点需要存储值、左右子节点指针。通常每个节点的大小为常数级别(例如包含一个整数值和两个指针)。 2. **版本管理**:每个版本通过一个根节点指针进行管理,根节点指针的存储开销为 $O(1)$。 3. **路径复制**:每次更新操作会生成新的节点,这些新节点的总数为 $O(\log n)$,因此总内存占用为 $O(n \log n)$。 在实际应用中,内存占用还可能受到编程语言的内存管理机制影响。例如,在 C++ 中手动管理内存可能导致较高的内存碎片,而在 Java 或 Python 等具有垃圾回收机制的语言中,内存占用可能相对较低,但具体表现取决于实现细节。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值