可持久化线段树(主席树)

· · 算法·理论

省流:主要思想:离散化,前缀和,动态开点和线段树上二分。

模板题:P3834。

大体思想

一看到区间问题,首先会想到线段树。但是一棵线段树无法维护区间第 k 小。

动态开点

虽然时间复杂度很不错,为 \mathcal O((n + m) \log n),但是空间复杂度很劣,为 \mathcal O(n^2)

考虑优化。我们第 i 棵线段树的信息需要靠第 i-1 棵线段树去维护,其中被修改的叶子节点只有 1 个,所以真正与第 i-1 棵线段树的不同位置构成了一条链,数量为 \mathcal O(\log n)

动态开点的本质是将没有发生改动的节点进行重复利用,从第 i 棵线段树的节点连到第 i-1 棵线段树的这些节点。

每次新增节点至多增加 \mathcal O(\log n) 个,故此时空间复杂度为 \mathcal O(n \log n)

小细节:最开始可以建一棵完整的空权值线段树也可以不建,因为最后那些没有用到的节点权值为 0,不建不会影响最终答案。

模板题代码

#include<bits/stdc++.h>
#define int long long
#define rep(i,l,r) for(int i=l;i<=r;++i)
using namespace std;

const int N=2e5+5;
int n,m,tot=0,sz;
int a[N],b[N],rt[N];

struct tree{
    int l,r,val;
}t[N<<5];

int change(int u,int l,int r,int x){
    int p=++tot;
    int mid=(l+r)>>1;
    t[p].val=t[u].val+1;
    if(l<r){
        if(x<=mid){
            t[p].r=t[u].r;
            t[p].l=change(t[u].l,l,mid,x);
        }else{
            t[p].l=t[u].l;
            t[p].r=change(t[u].r,mid+1,r,x);
        }
    }
    return p;
}

int query(int u,int v,int l,int r,int k){
    if(l==r)return l;
    int sum=t[t[v].l].val-t[t[u].l].val;
    int mid=(l+r)>>1;
    if(k<=sum)return query(t[u].l,t[v].l,l,mid,k);
    return query(t[u].r,t[v].r,mid+1,r,k-sum);
}

signed main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cin>>n>>m;
    rep(i,1,n){
        cin>>a[i];
        b[i]=a[i];
    }
    sort(b+1,b+n+1);
    sz=unique(b+1,b+n+1)-b-1;
    rep(i,1,n){
        int x=lower_bound(b+1,b+sz+1,a[i])-b;
        rt[i]=change(rt[i-1],1,sz,x);
    }
    while(m--){
        int l,r,k;
        cin>>l>>r>>k;
        int sum=query(rt[l-1],rt[r],1,sz,k);
        cout<<b[sum]<<"\n";
    }
    return 0;
}

关于可持久化线段树的补充(deepseek)

可持久化线段树(Persistent Segment Tree)之所以称为“可持久化”,是因为它能够保留所有历史版本,并在每次修改后生成一个新版本,同时允许高效地访问和查询任意历史版本的数据。以下是其核心原理:

1. 核心思想:路径复制与共享

2. 关键特性

3. 应用场景

4. 示意图

  版本1根              版本2根
    │                    │
    A                    A'(复制并修改)
   / \                  / \
  B   C               B'   C(共享未修改分支)
 /     \             /     \
D       E           D       E

图1:修改节点 B 时,仅复制路径 A \rightarrow B \rightarrow D,生成新节点 A'B',而 CE 未被修改,直接共享。

5. 总结

可持久化线段树通过路径复制节点共享,在保证高效操作的同时保留所有历史版本,从而实现了“可持久化”。这种设计在算法优化和特定问题场景(如离线查询、版本控制)中具有重要价值。

函数式编程的简单概念(deepseek)

函数式编程(Functional Programming,FP)是一种编程范式,其核心思想是将计算视为数学函数的求值,并强调不可变数据无副作用的操作。