可持久化线段树

给定一个序列 求在 li ri 之间的 数值大小在 【a,b】 的数的个数 有多少个 ?

山东省省赛题目。  当时离线直接水过。 

http://acm.upc.edu.cn/problem.php?id=2224

#include <vector>
#include <list>
#include <map>
#include <set>
#include <deque>
#include <stack>
#include <cstring>
#include <bitset>
#include <algorithm>
#include <functional>
#include <numeric>
#include <utility>
#include <sstream>
#include <iostream>
#include <iomanip>
#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <ctime>
#include <assert.h>
#include <queue>
#define REP(i,n) for(int i=0;i<n;i++)
#define TR(i,x) for(typeof(x.begin()) i=x.begin();i!=x.end();i++)
#define ALLL(x) x.begin(),x.end()
#define SORT(x) sort(ALLL(x))
#define CLEAR(x) memset(x,0,sizeof(x))
#define FILLL(x,c) memset(x,c,sizeof(x))
using namespace std;
const double eps = 1e-9;
#define LL long long 
#define pb push_back
const int maxn = 100010;
int n,m;
int num[maxn];
struct SegNode {
     SegNode *l,*r;
     int sum;
}nodes[maxn*25];
int C;
SegNode *root[maxn];
struct segmentTree{
	 SegNode *null;
	 void init(){
	 	C = 0;
	 	 null = root[0] = &nodes[C];
	 	 C++;
	 	 null->l = null;
	 	 null->r = null;
	 	 null ->sum = 0;
	 }
	 SegNode * update(int pos,int left ,int right ,SegNode *root,int val){
	 	   SegNode  *rt = &nodes[C++];
	 	   rt->l = root->l;
	 	   rt->r = root->r;
	 	   rt->sum = root->sum;
	 	   if(left == right && pos == left ){
	 	   	     rt->sum +=val;
	 	   	     return rt;
	 	   }
	 	  
	 	   int mid = (left +right) >>1;
	 	   if(pos<=mid){
	 	   	    rt->l = update(pos,left ,mid,root->l,val);
	 	   }else{
	 	   	    rt->r = update(pos,mid+1,right,root->r,val);
	 	   }
	 	   rt->sum = rt->l->sum + rt->r->sum;
	 	   return rt;
	 }
	 int query(int left ,int right ,int L ,int R,SegNode *root){
	 	 
	 	 if(L<= left  && right  <= R){
	 	 	   return root->sum;
	 	 }
	 	 int mid= (left +right)/2;
	 	 int ret = 0;
	 	 if(L<=mid){
	 	 	 ret+= query(left,mid,L,R,root->l);
	 	 }
	 	 if(R>mid){
	 	 	 ret+= query(mid+1,right,L,R,root->r);
	 	 }
	 	 return ret;
	 }
}T;
int ql[maxn],qr[maxn],a[maxn],b[maxn];
map<int ,int >mp;
map<int, int>::iterator it;
int main(){
   int t ;
   cin >>t ;
   int cas = 0 ;
   while(~scanf("%d%d",&n,&m)){
   	cas ++ ;
       printf("Case #%d:\n",cas);
   	   mp.clear();
   	   for(int i =1;i<=n;i++){
   	   	   scanf("%d",&num[i]);
   	   	   mp[num[i]] = 1;
   	   }
   	   
   	   for(int i=1;i<=m;i++){
   	   	  scanf("%d%d%d%d",&ql[i],&qr[i],&a[i],&b[i]);
   	      mp[a[i]] =1;
   	      mp[b[i]] =1;
   	   }
   	   int tot = 0 ;
   	   for(it = mp.begin();it!= mp.end();it++){
   	   	    it->second = ++tot;
   	   }
   	   T.init();
   	   for(int i=1;i<= n;i++){
   	   	   root[i] = T.update(mp[num[i]],1,tot,root[i-1],1);
   	   }
   	   for(int i=1;i<=m;i++){
   	   //	 cout << mp[a[i]]<< "  "<<mp[b[i]] << "  "<<tot << "  "<<qr[i] <<"  "<<ql[i]<<endl;
   	   	    int ans = T.query(1,tot,mp[a[i]],mp[b[i]],root[qr[i]]) - T.query(1,tot,mp[a[i]],mp[b[i]],root[ql[i]-1]) ;
   	   	    printf("%d\n",ans);
   	   	  //  cout <<  <<endl;
   	   	 //  cout <<<<endl;
   	   }
   }
    return 0;
}

可持久化线段树是一种支持历史版本查询的数据结构,其核心思想是在每次修改操作时保留完整的旧版本信息。这使得它在某些应用场景中非常有用,例如版本控制系统或需要回溯操作的算法问题。 ### 空间复杂度分析 可持久化线段树的空间复杂度与普通线段树相比有所增加。普通线段树的空间复杂度为 $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 等具有垃圾回收机制的语言中,内存占用可能相对较低,但具体表现取决于实现细节。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值