首页 > 其他分享 >主席树

主席树

时间:2023-05-10 20:11:41浏览次数:47  
标签:rt pre cnt int mid Query 主席

可持久化线段树

值域线段树

  • 设线段树节点 \(i\) 管辖区间 \([l,r]\),\(i\) 的 \(val\) 表示 $ l\ge $ 且 $ \le r$ 的数的个数

  • 那么 \(i.l\) 表示$ l\ge $ 且 $ \le mid$ 的数的个数, \(i.r\) 表示$ mid+1\ge $ 且 $ \le r$ 的数的个数

  • 如果建 \(n\) 棵值域线段树,第 \(i\) 棵维护区间 \([1,i]\),那么查找 \([l,r]\) 区间的第 \(k\) 大就将第 \(r\) 棵树与第 \(l-1\) 棵树相减,得到的就是 \([l,r]\) 的信息(有点像差分哈)

  • 但是第 \(i\) 棵树与第 \(i-1\) 棵树的差别很小,可以只记录两棵树之间的差别

可持久化线段树

  • 操作:

    • Update

    • Query

  • 每次的Update就是在值域上加入一个点,只新建了一条长度为 \(log_2 n\) 的链,于是新的树就是在原树的基础上添加了一条链,其余的节点的信息直接复制过去就行

int Update(int pre,int l,int r,int val){
    int rt=++cnt;   //动态开点
    t[rt].cnt=t[pre].cnt; t[rt].l=t[pre].l; t[rt].r=t[pre].r;   //复制节点信息
    t[rt].cnt++;
    int mid=(l+r)/2;
    if(l<r){
        //连接新增的节点(将t[rt].l/t[rt].r与他们的新子节点连接)
        if(val<=mid) t[rt].l=Update(t[pre].l,l,mid,val);
        else t[rt].r=Update(t[pre].r,mid+1,r,val);
    }
    return rt;
}

for(int i=1;i<=n;i++){
    int pos=lower_bound(rk+1,rk+1+siz,in[i])-rk;
    rt[i]=Update(rt[i-1],1,siz,pos);    //记录这次修改的根节点
}

  • 图片来自博客园

  • 修改后新加上的链并在原来的树上,成了这个样子

  • Query操作将树 \(now\) 的信息减去树 \(l-1\) 的信息得到 \([l,r]\) 的信息

int Query(int pre,int now,int l,int r,int k){
    if(l==r) return l;
    int cnt=t[t[now].l].cnt-t[t[pre].l].cnt;
    int mid=(l+r)/2;
    if(cnt>=k)
        return Query(t[pre].l,t[now].l,l,mid,k);
    else 
        return Query(t[pre].r,t[now].r,mid+1,r,k-cnt);
}

for(int i=1;i<=m;i++){
    cin>>l>>r>>k;
    int pos=Query(rt[l-1],rt[r],1,siz,k);
    cout<<rk[pos]<<"\n";
}
  • 因为开的是值域线段树,所以如果值域很大(比如这题的 \(10^9\) ),需要离散化一下

https://www.luogu.com.cn/problem/P3834

  • 区间第 \(k\) 大/小问题

  • 方法同上面

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+10;

int n,m,siz;
int in[N],rk[N],l,r,k;
struct Node{
    int l,r,cnt;
}t[N<<5];
int cnt,rt[N];

int Update(int pre,int l,int r,int val){
    int rt=++cnt;
    t[rt].cnt=t[pre].cnt; t[rt].l=t[pre].l; t[rt].r=t[pre].r;
    t[rt].cnt++;
    int mid=(l+r)/2;
    if(l<r){
        if(val<=mid) t[rt].l=Update(t[pre].l,l,mid,val);
        else t[rt].r=Update(t[pre].r,mid+1,r,val);
    }
    return rt;
}

int Query(int pre,int now,int l,int r,int k){
    if(l==r) return l;
    int cnt=t[t[now].l].cnt-t[t[pre].l].cnt;
    int mid=(l+r)/2;
    if(cnt>=k)
        return Query(t[pre].l,t[now].l,l,mid,k);
    else 
        return Query(t[pre].r,t[now].r,mid+1,r,k-cnt);
}

signed main(){
    // freopen("1.in","r",stdin);
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        cin>>in[i];
        rk[i]=in[i];
    }
    sort(rk+1,rk+1+n);
    siz=unique(rk+1,rk+1+n)-rk-1;
    for(int i=1;i<=n;i++){
        int pos=lower_bound(rk+1,rk+1+siz,in[i])-rk;
        rt[i]=Update(rt[i-1],1,siz,pos);
    }
    for(int i=1;i<=m;i++){
        cin>>l>>r>>k;
        int pos=Query(rt[l-1],rt[r],1,siz,k);
        cout<<rk[pos]<<"\n";
    }
}

https://www.luogu.com.cn/problem/P3919

  • 区间更新问题

  • 操作类似,每次操作会产生一个新版本

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+10;

int n,m;
int in[N],edit,opt,loc,val;
struct Node{
    int l,r,val;
}t[N<<5];
int cnt,rt[N];

void Clone(int now,int pre){
    t[now].l=t[pre].l;
    t[now].r=t[pre].r;
    t[now].val=t[pre].val;
}

int Build(int l,int r){
    int rt=++cnt;
    if(l==r){
        t[rt].val=in[l];
        return rt;
    }
    int mid=(l+r)/2;
    t[rt].l=Build(l,mid);
    t[rt].r=Build(mid+1,r);
    return rt;
}

int Update(int id,int l,int r,int loc,int val){
    int rt=++cnt;
    Clone(rt,id);
    if(l==r){
        t[rt].val=val;
        return rt;
    }
    int mid=(l+r)/2;
    if(loc<=mid) t[rt].l=Update(t[id].l,l,mid,loc,val);
    else t[rt].r=Update(t[id].r,mid+1,r,loc,val);
    return rt;
}

int Query(int id,int l,int r,int loc){
    if(l==r)
        return t[id].val;
    int mid=(l+r)/2;
    if(loc<=mid) return Query(t[id].l,l,mid,loc);
    else return Query(t[id].r,mid+1,r,loc);
}

signed main(){
    // freopen("1.in","r",stdin);
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);

    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>in[i];
    rt[0]=Build(1,n);
    for(int i=1;i<=m;i++){
        cin>>edit>>opt>>loc;
        if(opt==1){
            cin>>val;
            rt[i]=Update(rt[edit],1,n,loc,val);
        }else{
            cout<<Query(rt[edit],1,n,loc)<<"\n";
            rt[i]=rt[edit];
        }
    }
}

https://www.luogu.com.cn/problem/P4587

  • 逐个枚举每个可能的神秘数

  • 假设现在可以表示出神秘数 \([1,x]\),那么如果添加集合 \(S\) 的元素 \(k\):

    • \(k \le x+1\),那么现在可以表示 \([1,x] \cup [1+k,x+k] = [1,x+k]\)

    • \(k \gt x+1\),那么可以表示的数的集合不连续,最小的非神秘数就是 \(x+1\)

  • 优化一下:

    • 如果值域在 \([1,x]\) 的集合 \(S\) 的元素能表示神秘数 \([1,y]\),那么放进去 \([1,y+1]\) 的数都是合法的

    • 值域在 \([1,x]\) 的数能表示的神秘数的和为 \(i=sum([1,x])\),值域在 \([1,y+1]\) 的数能表示的神秘数的和为 \(j=sum([1,y+1])\),如果 $i < j $,则区间 \([1,y]\) 中有 \(j-i\) 的数还没用,把他们都放进去,现在能表示的神秘数变为 \([1,j]\)

    • 求和的事情用主席树来干就行

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int INF=1e9+10;
const int N=1e6+10;

int n,m;
int in[N],l,r;
struct Node{
    int l,r,val;
}t[N<<5];
int cnt,rt[N];

int Update(int pre,int l,int r,int val){
    int rt=++cnt;
    t[rt]=t[pre]; t[rt].val+=val;
    if(l==r) return rt;
    int mid=(l+r)/2;
    if(val<=mid) t[rt].l=Update(t[pre].l,l,mid,val);
    else t[rt].r=Update(t[pre].r,mid+1,r,val);
    return rt;
}

int Query(int pre,int now,int l,int r,int ql,int qr){
    if(ql<=l && r<=qr) return t[now].val-t[pre].val;
    int mid=(l+r)/2,ans=0;
    if(ql<=mid) ans+=Query(t[pre].l,t[now].l,l,mid,ql,qr);
    if(qr>mid) ans+=Query(t[pre].r,t[now].r,mid+1,r,ql,qr);
    return ans;
}

signed main(){
    // freopen("1.in","r",stdin);
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>in[i];
        rt[i]=Update(rt[i-1],1,INF,in[i]);
    }
    cin>>m;
    for(int i=1;i<=m;i++){
        cin>>l>>r;
        int ans=1,res;
        while(true){
            res=Query(rt[l-1],rt[r],1,INF,1,ans);
            if(res>=ans) ans=res+1;
            else break;
        }
        cout<<ans<<"\n";
    }
}

标签:rt,pre,cnt,int,mid,Query,主席
From: https://www.cnblogs.com/wangyangjena/p/17389204.html

相关文章

  • WWW‘23 | Apr 30-May 4,交叉新兴领域顶级会议!清华唐杰任大会主席!
     WWW,曾用名为TheInternationalConferenceofWorldWideWeb,从2018年开始更名为Thewebconference。WWW为交叉,新兴,综合领域的顶级会议,CCFA类,CoreConferenceRankingA*类会议,H5指数80,ImpactScore14.69。会议的目的是为全世界的互联网领域研究人员提供一个学术交流平台,共同......
  • 主席树 学习笔记
    考试的时候用到了,顺便学习一下。upd:2023.04.21终于把坑填了。0x00前言主席树(又称可持久化线段树,函数式线段树)是一种常用的数据结构。它以保存每次修改时的历史版本为主要思想,拥有大量的应用场景(可持久化trie/并查集/数组\(\ldots\))(当然,常数也是很大的)。0x01引入例题:HDU2......
  • 主席树学习笔记
    主席树,又名可持久化线段树,可以访问多个历史版本的树上存的信息。图及其他来源于此:https://www.cnblogs.com/hyfhaha/p/10678275.html基本思想用到的基本思想就是对于每一个修改版本的树,只新建修改后的节点,如果是每一个版本新开一个线段树的话空间一定不够。这是普通的线段树......
  • 可持久化线段树(主席树)
              代码#include<bits/stdc++.h>usingnamespacestd;constintN=4e7+10;intn,m,t,top,rt,mode,x,y;intf[N],a[N],root[N];structkkk{intl,r,val;}tree[N];intclone(intnode){top++;tree[top]=tree[node];return......
  • Codeforces Round 368 (Div. 2) D. Persistent Bookcase 主席树维护bitset
    在学主席树时找到了这道题本来yyyy了一个二维的主席树这种东西,然后发现很多信息好像维护不了观察到n和m都很小,考虑把一整行看成一个节点,开一个bitset然后区间取反、单点......
  • Codeforces Round 406 (Div. 1) C. Till I Collapse 主席树上二分
    首先贪心是显然的,但是询问有n个先考虑一个朴素的主席树做法对于每个k,对于当前固定的L,二分R,用主席树查询一下[L,R]区间内的不同数个数是否大于K个(主席树的经典应用),更新......
  • 【YBT2023寒假Day12 A】我的世界(二分)(主席树)
    我的世界题目链接:YBT2023寒假Day12A题目大意有n个数,每一秒每个数都会减小1,而且你可以选一个数让它减小x,小于0的数会变成0。给你s秒,问你s秒操作后所有数中......
  • 主席树
    有时间再修P3919【模板】可持久化线段树1(可持久化数组)点击查看代码#include<bits/stdc++.h>#defineintlonglong#definecsconst#defineilinline#defineri......
  • 【代码源 Div1 - 108】#464. 数数(主席树,区间比k小的数的个数)HDU4417
    problemsolution主席树查询区间比k小的数的个数建树之后直接在目标区间的主席树内将H作为挡板递归计数。#include<bits/stdc++.h>usingnamespacestd;#defineIOSios::s......
  • 2019年ICPC南昌网络赛 J. Distance on the tree(树链剖分+主席树 查询路径边权第k大)
    DSM(DataStructureMaster)oncelearnedabouttreewhenhewaspreparingforNOIP(NationalOlympiadinInformaticsinProvinces)inSeniorHighSchool.Sowhen......