首页 > 其他分享 >【学习笔记】 - 可持久化数据结构初步:可持久化线段树

【学习笔记】 - 可持久化数据结构初步:可持久化线段树

时间:2024-01-30 11:01:04浏览次数:30  
标签:ch 持久 Val Get 线段 Fast Istream operator 数据结构

前置知识:

  • 权值线段树

    权值线段树每个节点不再是区间,而是值在某个范围内的个数

    可以用于求区间第 \(k\) 大值

  • 动态开点

    一个点只有在需要时才被创建

正文

什么是可持久化数据结构

就是说这个数据结构能保留每一个历史版本且支持操作

可持久化线段树

又称函数式线段树/主席树

朴素的想法是对于每次修改,都建立一颗新的线段树,复杂度较高

如图所示,可持久化线段树通过创建新的节点来记录历史版本,每次都基于原本的线段树创建一条新的链,时间复杂度较优\(\text O((n+m)\log n)\),空间 \(\text O((2n-1)+n(\lceil\log_2{n}\rceil+1))\)

特殊的,主席树可以维护查询静态区间 \(k\) 小值操作

image

大概是这样的一张图?

最开始只有根节点(\(1\))

插入\(2\)后建立包括 \(\{1',2\}\) 的历史版本 \(1'\)

插入\(4\)后建立包括 \(\{1'',2',4\}\) 的历史版本 \(1''\)

插入\(5\)后建立包括 \(\{1''',2'',4,5\}\) 的历史版本 \(1'''\)

插入\(3\)后建立包括 \(\{1'''',2'',4,5,3\}\) 的历史版本 \(1''''\)

插入\(6\)后建立包括 \(\{1''''',2'',4,5,3',6\}\) 的历史版本 \(1'''''\)

插入\(7\)后建立包括 \(\{1'''''',2'',4,5,3'',6,7\}\) 的历史版本 \(1''''''\)

这么多的权值线段树就可以大力维护区间第 \(k\) 小值了

对于查询\(l \sim r\)区间,我们可以用\(1\sim r\)减去\(1\sim l-1\)

天依的代码
#include<bits/stdc++.h>
#define N 0x66ccff
#define sort std::stable_sort
namespace Fast_I {char *_Buf, *_Start_ptr, *_End_ptr;std::streambuf* inbuf;unsigned int Size;bool _Ok;struct Fast_Istream {operator bool(){return _Ok; }Fast_Istream(std::streambuf*, unsigned int);Fast_Istream(unsigned int);Fast_Istream(const char*, unsigned int);Fast_Istream& operator>>(char&);Fast_Istream& operator>>(char*);Fast_Istream& operator>>(bool&);Fast_Istream& operator>>(short&);Fast_Istream& operator>>(int&);Fast_Istream& operator>>(long&);Fast_Istream& operator>>(long long&);Fast_Istream& operator>>(unsigned short&);Fast_Istream& operator>>(unsigned int&);Fast_Istream& operator>>(unsigned long&);Fast_Istream& operator>>(unsigned long long&);Fast_Istream& operator>>(float&);Fast_Istream& operator>>(double&);Fast_Istream& operator>>(long double&);Fast_Istream& operator>>(std::string&);template <typename Typex>void operator()(Typex& _Val) { *this >> _Val; }template <typename Typex, typename... More>void operator()(Typex&, More&...);std::streambuf* rdbuf() { return inbuf; }void rdbuf(std::streambuf* _inbuf) { inbuf = _inbuf; }void rdbuf(const char*);void pop();char peek();};}
namespace Fast_O {std::string buf;std::streambuf* outbuf;struct Fast_Ostream {Fast_Ostream(std::streambuf*, unsigned int);Fast_Ostream(std::streambuf* out) { outbuf = out; }Fast_Ostream(const char*, unsigned int);Fast_Ostream(unsigned int);void flush();~Fast_Ostream();void endl() { buf.push_back('\n'); }template <typename Typex>void endl(const Typex& _Val);template <typename Typex, typename... More>void endl(const Typex&, const More&...);template <typename Typex>void operator()(const Typex& _Val);template <typename Typex, typename... More>void operator()(const Typex&, const More&...);Fast_Ostream& operator<<(char);Fast_Ostream& operator<<(const char*);Fast_Ostream& operator<<(const std::string&);Fast_Ostream& operator<<(bool);Fast_Ostream& operator<<(short);Fast_Ostream& operator<<(int);Fast_Ostream& operator<<(long);Fast_Ostream& operator<<(long long);Fast_Ostream& operator<<(unsigned short);Fast_Ostream& operator<<(unsigned int);Fast_Ostream& operator<<(unsigned long);Fast_Ostream& operator<<(unsigned long long);std::streambuf* rdbuf() { return outbuf; }void rdbuf(std::streambuf* _outbuf) { outbuf = _outbuf; }void rdbuf(const char*);};}
namespace Fast_IO {Fast_I::Fast_Istream fin(std::cin.rdbuf(), 1048576);Fast_O::Fast_Ostream fout(std::cout.rdbuf()); }
#define cin Fast_IO::fin
#define cout Fast_IO::fout
namespace Fast_I {Fast_Istream::Fast_Istream(std::streambuf* in, unsigned int Sz) {_Ok = 1;Fast_I::Size = Sz;inbuf = in;_Start_ptr = _End_ptr = _Buf = new char[Sz];}Fast_Istream::Fast_Istream(const char* in, unsigned int Sz) {_Ok = 1;Fast_I::Size = Sz;rdbuf(in);_Start_ptr = _End_ptr = _Buf = new char[Sz];}Fast_Istream::Fast_Istream(unsigned int Sz) {_Ok = 1;Fast_I::Size = Sz;_Start_ptr = _End_ptr = _Buf = new char[Sz];}void Fast_Istream::rdbuf(const char* File) {static std::ifstream __In__(File);rdbuf(__In__.rdbuf());}void Get_Char(char& _Val) {if (_Start_ptr == _End_ptr) {_Start_ptr = _Buf;_End_ptr = _Buf + inbuf->sgetn(_Buf, Size);}if (_Start_ptr == _End_ptr) {_Val = -1;_Ok = 0;} else {_Val = *_Start_ptr++;}}Fast_Istream& Fast_Istream::operator>>(char& _Val) {if(_Ok){Get_Char(_Val);while (_Val == 32 || _Val == 10 || _Val == 13 || _Val == 8 || _Val == 9 || _Val == 7 || _Val == 12 || _Val == 11) {Get_Char(_Val);}}return *this;}Fast_Istream& Fast_Istream::operator>>(char* _Val) {if (_Ok) {Get_Char(*_Val);while (*_Val == 32 || *_Val == 10 || *_Val == 13 || *_Val == 8 ||*_Val == 9 || *_Val == 7 || *_Val == 12 || *_Val == 11) {Get_Char(*_Val);}while (*_Val != 32 && *_Val != 10 && *_Val && *_Val != -1 && *_Val != 9 &&*_Val != 11 && *_Val != 12) {Get_Char(*++_Val);}*_Val = 0;--_Start_ptr;}return *this;}Fast_Istream& Fast_Istream::operator>>(std::string& _Val) {if (_Ok) {char c;Get_Char(c);while (c == 32 || c == 10 || c == 13 || c == 8 || c == 9 || c == 7 ||c == 12 || c == 11) {Get_Char(c);}for (_Val.clear();c != 32 && c != 10 && c && c != -1 && c != 9 && c != 11 && c != 12;Get_Char(c)) {_Val.push_back(c);}--_Start_ptr;}return *this;}template <typename Typex>void Get_Int(Typex& _Val) {if (_Ok) {char ch;bool _F = 0;for (Get_Char(ch); (ch < 48 || ch > 57) && ch != -1; Get_Char(ch)) {_F = ch == 45;}for (_Val = 0; ch > 47 && ch < 58 && ch != -1; Get_Char(ch)) {_Val = _Val * 10 + (ch ^ 48);}if (_F) {_Val = ~_Val + 1;}--_Start_ptr;}}template <typename Typex>void Get_Unsigned(Typex& _Val) {if (_Ok) {char ch;Get_Char(ch);while ((ch < 48 || ch > 57) && ch != -1) {Get_Char(ch);}for (_Val = 0; ch > 47 && ch < 58 && ch != -1; Get_Char(ch)) {_Val = _Val * 10 + (ch ^ 48);}--_Start_ptr;}}template <typename Typex>void Get_Double(Typex& _Val) {if(_Ok){char ch;bool _F = 0;for (Get_Char(ch); (ch < 48 || ch > 57) && ch != -1; Get_Char(ch)) {_F = ch == 45;}for (_Val = 0; ch > 47 && ch < 58 && ch != -1; Get_Char(ch)) {_Val = _Val * 10 + (ch ^ 48);}if (ch == 46) {unsigned long long _Pow = 1;for (Get_Char(ch); ch > 47 && ch < 58 && ch != -1; Get_Char(ch)) {_Val += Typex((ch ^ 48) * 1.0 / (_Pow *= 10));}}if (_F) {_Val = -_Val;}--_Start_ptr;}}Fast_Istream& Fast_Istream::operator>>(bool& _Val) {if(_Ok){char ch;Get_Char(ch);while (ch == 32 || ch == 10 || ch == 13 || ch == 8 || ch == 9 || ch == 7 ||ch == 12 || ch == 11) {Get_Char(ch);}while (ch != 32 && ch != 10 && ch && ch != -1 && ch != 9 && ch != 11 &&ch != 12) {_Val |= ch != 48;Get_Char(ch);}--_Start_ptr;}return *this;}Fast_Istream& Fast_Istream::operator>>(short& _Val) {Get_Int(_Val);return *this;}Fast_Istream& Fast_Istream::operator>>(int& _Val) {Get_Int(_Val);return *this;}Fast_Istream& Fast_Istream::operator>>(long& _Val) {Get_Int(_Val);return *this;}Fast_Istream& Fast_Istream::operator>>(long long& _Val) {Get_Int(_Val);return *this;}Fast_Istream& Fast_Istream::operator>>(unsigned short& _Val) {Get_Unsigned(_Val);return *this;}Fast_Istream& Fast_Istream::operator>>(unsigned int& _Val) {Get_Unsigned(_Val);return *this;}Fast_Istream& Fast_Istream::operator>>(unsigned long& _Val) {Get_Unsigned(_Val);return *this;}Fast_Istream& Fast_Istream::operator>>(unsigned long long& _Val) {Get_Unsigned(_Val);return *this;}Fast_Istream& Fast_Istream::operator>>(float& _Val) {Get_Double(_Val);return *this;}Fast_Istream& Fast_Istream::operator>>(double& _Val) {Get_Double(_Val);return *this;}Fast_Istream& Fast_Istream::operator>>(long double& _Val) {Get_Double(_Val);return *this;}template <typename Typex, typename... More>void Fast_Istream::operator()(Typex& _Val, More&... _More) {*this >> _Val;operator()(_More...);}void Fast_Istream::pop() {char ch;Get_Char(ch);}char Fast_Istream::peek() {if (_Start_ptr == _End_ptr) {_Start_ptr = _Buf;_End_ptr = _Buf + inbuf->sgetn(_Buf, Size);}if (_Start_ptr == _End_ptr) {_Ok = 0;return -1;} else {return *_Start_ptr;}}}
namespace Fast_O {Fast_Ostream::Fast_Ostream(std::streambuf* out, unsigned int Size) {buf.reserve(Size);outbuf = out;}Fast_Ostream::Fast_Ostream(const char* File, unsigned int Size) {buf.reserve(Size);rdbuf(File);}void Fast_Ostream::rdbuf(const char* File) {static std::ofstream __Out__(File);rdbuf(__Out__.rdbuf());}Fast_Ostream::Fast_Ostream(unsigned int Size) {buf.reserve(Size);}void Fast_Ostream::flush() {outbuf->sputn(buf.data(), buf.size());buf.clear();}Fast_Ostream::~Fast_Ostream() {flush();}Fast_Ostream& Fast_Ostream::operator<<(char _Val) {buf.push_back(_Val);return *this;}Fast_Ostream& Fast_Ostream::operator<<(const char* _Val) {while (*_Val) {buf.push_back(*_Val++);}return *this;}Fast_Ostream& Fast_Ostream::operator<<(const std::string& _Val) {for (auto&& i : _Val) {buf.push_back(i);}return *this;}template <typename Typex>void Put_Unsigned(Typex _Val) {char* _Stack = (char*)malloc(sizeof(Typex) * 3);unsigned S_top = 0;while (_Val) {_Stack[++S_top] = (_Val % 10) ^ 48;_Val /= 10;}if (!S_top) {buf.push_back('0');}while (S_top) {buf.push_back(_Stack[S_top--]);}free(_Stack);}void Put_Int(long long _Val) {if (_Val < 0) {buf.push_back('-');Put_Unsigned(~_Val + 1);} else {Put_Unsigned(_Val);}}Fast_Ostream& Fast_Ostream::operator<<(bool _Val) {buf.push_back(_Val ? '1' : '0');return *this;}Fast_Ostream& Fast_Ostream::operator<<(short _Val) {Put_Int(_Val);return *this;}Fast_Ostream& Fast_Ostream::operator<<(int _Val) {Put_Int(_Val);return *this;}Fast_Ostream& Fast_Ostream::operator<<(long _Val) {Put_Int(_Val);return *this;}Fast_Ostream& Fast_Ostream::operator<<(long long _Val) {Put_Int(_Val);return *this;}Fast_Ostream& Fast_Ostream::operator<<(unsigned short _Val) {Put_Unsigned(_Val);return *this;}Fast_Ostream& Fast_Ostream::operator<<(unsigned int _Val) {Put_Unsigned(_Val);return *this;}Fast_Ostream& Fast_Ostream::operator<<(unsigned long _Val) {Put_Unsigned(_Val);return *this;}Fast_Ostream& Fast_Ostream::operator<<(unsigned long long _Val) {Put_Unsigned(_Val);return *this;}template <typename Typex>void Fast_Ostream::endl(const Typex& _Val) {*this << _Val << '\n';}template <typename Typex, typename... More>void Fast_Ostream::endl(const Typex& _Val, const More&... _More) {*this << _Val;endl(_More...);}template <typename Typex>void Fast_Ostream::operator()(const Typex& _Val) {*this << _Val;}template <typename Typex, typename... More>void Fast_Ostream::operator()(const Typex& _Val, const More&... _More) {*this << _Val;operator()(_More...);}}
#define lower_bound std::lower_bound
#define int long long
#define lc ls[q]
#define rc rs[q]
#define mid ((l+r)>>1)
int n,m,q[N<<1],len,a[N],tot;
int root[N],ls[N],rs[N<<1],sum[N<<1];
inline void build(int &q,int l,int r){
    q=++tot;
    if(l==r) return;
    build(lc,l,mid);
    build(rc,mid+1,r);
}
inline void newnode(int x,int y){
    ls[x]=ls[y];
    rs[x]=rs[y];
    sum[x]=sum[y]+1;
}
inline int change(int q,int l,int r,int x){
    int t=++tot;
    newnode(t,q);
    if(l==r) return t;
    if(mid>=x) ls[t]=change(ls[q],l,mid,x);
    else rs[t]=change(rs[q],mid+1,r,x);
    return t;
}
inline int ask(int rt,int q,int l,int r,int k){
    if(l==r) return l;
    int x=sum[lc]-sum[ls[rt]];
    if(x>=k) return ask(ls[rt],lc,l,mid,k);
    else return ask(rs[rt],rc,mid+1,r,k-x);
}
signed main(){
#ifndef ONLINE_JUDGE
    freopen("1.in","r",stdin);
    freopen("1.out","w",stdout);
#endif
    cin(n,m);
    for(int i=1;i<=n;++i)
        cin(a[i]),q[++len]=a[i];
    sort(q+1,q+len+1);
    len=std::unique(q+1,q+len+1)-q-1;
    build(root[0],1,len);
    for(int i=1;i<=n;++i){
        int t=lower_bound(q+1,q+len+1,a[i])-q;
        root[i]=change(root[i-1],1,len,t);
    }
    for(int i=1;i<=m;++i){
        int x,y,k;cin(x,y,k);
        cout(q[ask(root[x-1],root[y],1,len,k)],"\n");
    }
}

我们可以借可持久化线段树实现一些较简单的可持久化数据结构

  • 可持久化数组

    需要支持的操作:修改某一历史版本的某个值,查询某一历史版本的某个值

    • 代码

      天依的代码
      #include<bits/stdc++.h>
      #define int long long
      #define lc T[q].ls
      #define rc T[q].rs
      #define mid ((l+r)>>1)
      using namespace std;
      const int N=0x66CCFF*10;
      int a[N],n,q,rt[N],ans,m,v;
      class{
      public:
          int ls,rs,val;
      }T[N];
      inline int read(){
          int f=1,x=0;char ch;
          while(ch<'0'||ch>'9'){ch=getchar();if(ch=='-')f=-1;};
          while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();};
          return f*x;
      }
      inline void build(int &q,int l,int r){
          q=++ans;
          if(l==r){T[q].val=a[l];return;}
          build(lc,l,mid);
          build(rc,mid+1,r);
      }
      inline void change(int &q,int tot,int l,int r,int m,int v){
          q=++ans;
          lc=T[tot].ls;rc=T[tot].rs;
          T[q].val=T[tot].val;
          if(l==r){T[q].val=v;return;}
          if(m<=mid) change(lc,T[tot].ls,l,mid,m,v);
          else change(rc,T[tot].rs,mid+1,r,m,v);
      }
      inline int ask(int q,int l,int r,int ll,int rr){
          if(ll<=l&&rr>=r)return T[q].val;
          int val=0;
          if(ll<=mid)val+=ask(lc,l,mid,ll,rr);
          if(rr>mid)val+=ask(rc,mid+1,r,ll,rr);
          return val;
      }
      signed main(){
          // freopen("1.in","r",stdin);
          n=read(),m=read();
          for(int i=1;i<=n;i++)
              a[i]=read();
          build(rt[0],1,n);
          for(int i=1;i<=m;i++){
              int tot=read(),opt=read(),x=read();
              if(opt==1)
                  v=read();
                  change(rt[i],rt[tot],1,n,x,v);
              if(opt==2){
                  cout<<ask(rt[tot],1,n,x,x)<<"\n";
                  rt[i]=rt[tot];
              }
          }
      }
      
  • 可持久化并查集

    注意不能进行路径压缩,因为路径压缩一次会修改大量数组

    我们可以选择启发式合并

标签:ch,持久,Val,Get,线段,Fast,Istream,operator,数据结构
From: https://www.cnblogs.com/Vsinger-LuoTianYi/p/17996686

相关文章

  • 动态区间特殊值——线段树的简单应用
    目录问题引入简单介绍功能详情碎碎念问题引入很多地方写的是区间最值问题,感觉区间特殊值更好一些,区间gcd、前缀和都可以,问题描述大致就是给出一个数组,要求给出询问区间的特殊值,当然求和之类的可以用树状数组解决,但是最大、最小等等特殊值是不能转化为前缀和用树状数组解决的,因此......
  • 文件系统(二):分区、格式化数据结构
    liwen012024.01.28前言生活中,我们买回来的SD卡、TF卡、硬盘等存储设备一般是可以直接使用,如果要改变存储设备上的文件系统格式,我们一般直接在电脑上右键格式化就可以实现。买回来能直接用,是因为存储设备在出厂前厂家就已经做了分区和格式化操作。为什么存储设备需要分区格式......
  • 理解Set集合数据结构
    一、Set的基本概念Set是一种包含不重复元素的集合。与List(列表)不同,Set中的元素是无序的,不能通过索引来访问。Set中的每个元素都是唯一的,重复的元素将被自动剔除。二、Set的常见操作1.添加元素:使用add()方法向Set中添加新元素。如果添加的元素已经存在于Set中,则不会有任何改变......
  • 数据结构——队列链式存储实现
    队列链式存储主要有两个方面需要注意,一个是定义时应该定义两种结构体,一个是具体节点,一个是队列本身。具体节点用于存储具体数据data和指向下一个节点的指针*next。而队列本身的结构体只会储存两个具体节点的指针,一个指向队头,一个指向队尾。第二个需要注意的是,出队操作,对于只剩......
  • 【学习笔记】线段树
    1.线段树合并P4556雨天的尾巴首先我们可以把链上操作转成树上差分。然后我们对每个节点开一棵权值线段树。朴素的树上差分都是开一个桶然后加和,但是这里开的是线段树。所以就有了线段树合并。在把\(y\)并到\(x\)的过程中,如果\(x\)本身没有一个\(y\)有的节点就可以直......
  • 在K8S中,怎样实现数据持久化?
    在Kubernetes(简称K8s)中,数据持久化是通过Volume机制来实现的。Volume是一个抽象概念,它代表了Pod能够访问的存储资源,这些资源可以是本地磁盘、网络文件系统(NFS)、云提供商提供的块存储或对象存储等。以下是Kubernetes实现数据持久化的关键组件和过程:Volume:Volume为Pod提供了一......
  • 经典数据结构题目-树
    树105.从前序与中序遍历序列构造二叉树思路先序遍历中,树的根节点放在第一位,后面是左右子树。中序遍历中,树的根节点放在中间,两边分为左右子树可基于以上规则区分出每棵树在数组中的区间先从先序数组中拿到根节点的val因为val不重复,定位到该树在中序数组的位置中序数......
  • 数据结构笔记(1)
    开个博客记录一下算法学习的内容------------------------------------分界线------------------------------------最近在acwing上学了数据结构之链表,栈,队列,KMP(都是采用数组进行模拟,比用struct实现更快)链表:像一个链子一样一个元素串着另一个元素。单链表:每个节点有一个值......
  • 数据结构与算法:递归算法
    递归算法什么是递归?函数直接或间接调用自身的过程称为递归,相应的函数称为递归函数。使用递归算法,可以很容易地解决某些问题。此类问题的示例包括汉诺塔(TOH)、中序/先序/后序树遍历、图的DFS递归函数通过调用自身的副本并解决原始问题的较小子问题来解决特定问题。需要时可以生......
  • 线段树笔记
    voidpushup(inttr){ seg[tr]=seg[tr*2]+seg[tr*2+1];}voidbuild(inttr,intl,intr){ if(l==r){ seg[tr]=a[r]; return; } intmid=(l+r)/2; build(tr/2,l,mid); build(tr/2,mid+1,r); pushup(tr);}voidpushdown(inttr,intl,intr){ if(pls[tr]==0)......