【学习笔记】主席树

关于主席树的介绍有一篇博客很详细,适合入门:主席树入门
主席树需要的前置知识有:前缀和和线段树
1.主席树上的差分区间与前缀和有关,而主席树的建树,修改,查询和线段树很相似.学了线段树之后看主席树就显得没有那么困难。
2.由于主席树对于一般的线段树多了一个logn的空间的大小,因此需要注意空间防止MLE。主席树的用法非常灵活,主席树最重要的是他的算法是在线的
3.主席树是一个可持久化的线段树,可以对于历史的版本记录,可以查询之前版本的记录。
主席树建树时间复杂度:O(nlogn)
查询时间复杂度:O(mlogn)
总时间复杂度:O((n+m)logn)
空间复杂度:O(nlog^2 n)

1.建树
lc,rc表示左右子节点
sum 表示当前节点权值

void build(int &rt,int l,int r)
{
    rt=++sz,sum[rt]=0;
    if(l==r) return ;
    int mid=(l+r)>>1;
    build(lc[rt],l,mid),build(rc[rt],mid+1,r);
}

2.修改
每次update函数应首先将之前的左右子节点赋予给当前节点,然后在根据需要修改的位置重新创建左右子节点。在赋予左右节点时以将历史数据全部转移到当前的版本中。

int update(int o,int l,int r)
{
    int oo=++sz;
    lc[oo]=lc[o],rc[oo]=rc[o],sum[oo]=sum[o]+1; //权值+1
    if(l==r) return oo;
    int mid=(l+r)>>1;
    if(p<=mid) lc[oo]=update(lc[oo],l,mid);  //只对改动的点进行建点 p为本次修改的权值
    else rc[oo]=update(rc[oo],mid+1,r);
    return oo;
}

3.查询

int query(int u,int v,int l,int r,int k)
{
    int mid=(l+r)>>1;
    int x=sum[lc[v]]-sum[lc[u]];
    if(l==r) return l;
    if(x>=k) return query(lc[u],lc[v],l,mid,k);
    else return query(rc[u],rc[v],mid+1,r,k-x);
}

例题
主席树板子
思路:把query函数改一下

#include<bits/stdc++.h>
using namespace std;
typedef long long ll ;
typedef pair<int,int> PII;
typedef pair<ll,ll> PLL;
#define debug() cout<<"fuck      "<<endl
#define fr(i,k,n) for(int i=k;i<=n;i++)
#define fo(i,k,n) for(int i=n;i>=k;i--)
const int mod = 998244353,N=5e5+5,INF=0x3f3f3f3f;
const double eps=1e-6;
int n,m,p,q,sz=0,k,ans=0,pos;
int a[N],b[N];
int rt[N<<5],lc[N<<5],rc[N<<5],sum[N<<5];
char s[5];
inline int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}
    while(ch>='0'&& ch<='9') {x=x*10+ch-'0';ch=getchar();}
    return f*x;
}
void build(int &rt,int l,int r)
{
    rt=++sz,sum[rt]=0;
    if(l==r) return ;
    int mid=(l+r)>>1;
    build(lc[rt],l,mid),build(rc[rt],mid+1,r);
}
int update(int o,int l,int r)
{
    int oo=++sz;
    lc[oo]=lc[o],rc[oo]=rc[o],sum[oo]=sum[o]+1;
    if(l==r)
        return oo;
    int mid=(l+r)>>1;
    if(p<=mid) lc[oo]=update(lc[oo],l,mid);
    else rc[oo]=update(rc[oo],mid+1,r);
    return oo;
}
void query(int u,int v,int l,int r,int mm)
{
    int mid=(l+r)>>1;
    int x=sum[lc[v]]-sum[lc[u]];
    int y=sum[rc[v]]-sum[rc[u]];
    if(l==r)
    {
        pos=l;
        return ;
    }
    if(x>mm) query(lc[u],lc[v],l,mid,mm);
    else if(y>mm) query(rc[u],rc[v],mid+1,r,mm);
}

int main()
{
    std::ios::sync_with_stdio(false);
    cin.tie(0);
    cin>>n>>m;
    fr(i,1,n) cin>>a[i],b[i]=a[i];
    sort(b+1,b+n+1);
    q=unique(b+1,b+n+1)-b-1;
    build(rt[0],1,q);
    for(int i=1,j=1;i<=n;i++)
    {
        p=lower_bound(b+1,b+q+1,a[i])-b;
        rt[i]=update(rt[i-1],1,q);
    }
    int l,r;
    fr(i,1,m)
    {
        cin>>l>>r;
        int mm=(r-l+1)/2;
        pos=0;
        query(rt[l-1],rt[r],1,q,mm);
        printf("%d\n",b[pos]);
    }
    return 0;
}

P3919 【模板】可持久化线段树 1(可持久化数组)
思路:一个主席树的模板

#include<bits/stdc++.h>
using namespace std;
typedef long long ll ;
typedef pair<int,int> PII;
typedef pair<ll,ll> PLL;
#define debug() cout<<"fuck      "<<endl;
#define fr(i,k,n) for(int i=k;i<=n;i++)
#define fo(i,k,n) for(int i=n;i>=k;i--)
const int mod = 998244353,N=1e6+5,INF=1e9;
const double eps=1e-6;
int n,m,a[N],sz=0;
int rt[N*20],lc[N*20],rc[N*20],sum[N*20],val[N*20];
char s[5];
inline int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}
    while(ch>='0'&& ch<='9') {x=x*10+ch-'0';ch=getchar();}
    return f*x;
}
void build(int&rt,int l,int r)
{
    rt=++sz;
    if(l==r)
    {
        val[rt]=a[l];
        return ;
    }
    int mid=(l+r)>>1;
    build(lc[rt],l,mid);
    build(rc[rt],mid+1,r);
}
int update(int o,int l,int r,int x,int k)
{
    int oo=++sz;lc[oo]=lc[o],rc[oo]=rc[o];
    if(l==r)
    {
        val[oo]=k;
        return oo;
    }
    int mid=(l+r)>>1;
    if(x<=mid) lc[oo]=update(lc[oo],l,mid,x,k);
    else rc[oo]=update(rc[oo],mid+1,r,x,k);
    return oo;
}
int query(int u,int l,int r,int x)
{
    if(l==r)
    {
        return val[u];
    }
    int mid=(l+r)>>1;
    if(x<=mid) return query(lc[u],l,mid,x);
    else return query(rc[u],mid+1,r,x);
}
int main()
{
   n=read(),m=read();
   fr(i,1,n) a[i]=read();
   build(rt[0],1,n);
   fr(i,1,m)
   {
       int v=read(),op=read(),pos=read(),k;
       if(op==1)
       {
           k=read();
           rt[i]=update(rt[v],1,n,pos,k);
       }
       else
       {
           printf("%d\n",query(rt[v],1,n,pos));
           rt[i]=rt[v];
       }
   }
   return 0;
}

P3939 数颜色
思路:题目询问需要在线进行回答,因为询问的只是兔子的颜色,所以只需要修改rt[k]的中的节点即可。
因为若 l<k&&r<k 那么[l,r]的询问区间跟k的修改无关。若l<k&&r>k [l,r]的询问只和个数有关无关位置。若l>k 同样[l,r]询问与k无关。因此只需要修改k的位置。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll ;
typedef pair<int,int> PII;
typedef pair<ll,ll> PLL;
#define debug() cout<<"fuck      "<<endl
#define fr(i,k,n) for(int i=k;i<=n;i++)
#define fo(i,k,n) for(int i=n;i>=k;i--)
const int mod = 998244353,N=3e5+5,INF=0x3f3f3f3f;
const double eps=1e-6;
int n,m,p,q,sz=0,k,ans=0,pos[N];
int a[N],b[N];
int rt[N<<6],lc[N<<6],rc[N<<6],sum[N<<6];
char s[5];
inline int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}
    while(ch>='0'&& ch<='9') {x=x*10+ch-'0';ch=getchar();}
    return f*x;
}
int update(int o,int l,int r,int val)
{
    int oo=++sz;
    lc[oo]=lc[o],rc[oo]=rc[o],sum[oo]=sum[o]+val;
    if(l==r)
        return oo;
    int mid=(l+r)>>1;
    if(p<=mid) lc[oo]=update(lc[oo],l,mid,val);
    else rc[oo]=update(rc[oo],mid+1,r,val);
    return oo;
}
int query(int u,int v,int l,int r,int mm)
{
    int mid=(l+r)>>1;
    if(l==r)
        return sum[v]-sum[u];
    if(mm<=mid) return query(lc[u],lc[v],l,mid,mm);
    else return query(rc[u],rc[v],mid+1,r,mm);
}
int main()
{
    std::ios::sync_with_stdio(false);
    cin.tie(0);
    cin>>n>>m;
    fr(i,1,n) cin>>a[i],b[i]=a[i];
    sort(b+1,b+n+1);
    q=unique(b+1,b+n+1)-b-1;
    for(int i=1,j=1;i<=n;i++)
    {
        p=lower_bound(b+1,b+q+1,a[i])-b;
        pos[a[i]]=p;
        rt[i]=update(rt[i-1],1,q,1);
    }
    int l,r,op,k;
    fr(i,1,m)
    {
        cin>>op;
        if(op==1)
        {
            cin>>l>>r>>k;
            if(!pos[k])
            {
                printf("0\n");
                continue;
            }
            printf("%d\n",query(rt[l-1],rt[r],1,q,pos[k]));
        }
        else
        {
            ///询问的是颜色为k的兔子个数和兔子位置无关
            ///只修改rt[k]即可
            cin>>k;
            if(a[k]==a[k+1]) continue;
            p=lower_bound(b+1,b+q+1,a[k])-b;
            rt[k]=update(rt[k],1,q,-1);
            p=lower_bound(b+1,b+q+1,a[k+1])-b;
            rt[k]=update(rt[k],1,q,1);
            swap(a[k],a[k+1]);
        }
    }
    return 0;
}

2021牛客暑期多校训练营9 E题
思路:从首都到其他城市可看作一棵树,父节点的温度比子节点高,因此先用dfs记录每个节点子树范围,倍增祖先节点。每次查询时找到温度<r的祖先节点,然后在祖先节点进行查询子树范围内符合温度 L<=t&&t<=R的节点。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll ;
typedef pair<int,int> PII;
typedef pair<ll,ll> PLL;
#define debug() cout<<"fuck      "<<endl
#define fr(i,k,n) for(int i=k;i<=n;i++)
#define fo(i,k,n) for(int i=n;i>=k;i--)
const int mod = 998244353,N=5e5+5,INF=0x3f3f3f3f;
const double eps=1e-6;
int n,m,k,ans=0,cnt=0,tot=0,sz=0;
int a[N],pos[N],dfn[N],edn[N],fa[N][21],rt[N];
int lc[N*20],rc[N*20],sum[N*20];
char s[5];
vector<int>h[N];
struct node
{
    int l,r,val;
}b[N*4];
inline int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}
    while(ch>='0'&& ch<='9') {x=x*10+ch-'0';ch=getchar();}
    return f*x;
}
void dfs(int x,int f)  ///求出子树的起点和终点
{
    dfn[x]=++cnt;
    pos[cnt]=x;
    fa[x][0]=f;
    fr(i,1,20) fa[x][i]=fa[fa[x][i-1]][i-1];///倍增祖父节点
    fr(i,0,h[x].size()-1)
    {
        int y=h[x][i];
        if(y==f) continue;
        dfs(y,x);
    }
    edn[x]=cnt;
}
void update(int u,int &v,int l,int r,int pos)
{
    v=++sz;
    lc[v]=lc[u],rc[v]=rc[u],sum[v]=sum[u]+1;
    if(l==r) return ;
    int mid=(l+r)>>1;
    if(pos<=mid) update(lc[u],lc[v],l,mid,pos);
    else update(rc[u],rc[v],mid+1,r,pos);
}
int query(int u,int l,int r,int L,int R)
{
    if(u==0) return 0;
    if(L<=l&&r<=R)
    {
        return sum[u];
    }
    if(l>R||r<L) return 0;
    int mid=(l+r)>>1;
    return query(lc[u],l,mid,L,R)+query(rc[u],mid+1,r,L,R);
}

int main()
{
    std::ios::sync_with_stdio(false);
    cin.tie(0);
    cin>>n;
    fr(i,1,n-1)
    {
        int x,y;
        cin>>x>>y;
        h[x].push_back(y);
        h[y].push_back(x);
    }
    fr(i,1,n) cin>>a[i];
    dfs(1,0);
    fr(i,1,n)
       update(rt[i-1],rt[i],1,1e9,a[pos[i]]);
    m=read();
    fr(i,1,m)
    {
        int x,l,r;
        cin>>x>>l>>r;
        if(a[x]>r||a[x]<l)
        {
            puts("0");
            continue;
        }
        fo(i,0,20)
        if(a[fa[x][i]]<=r&&fa[x][i])
            x=fa[x][i];
        printf("%d\n",query(rt[edn[x]],1,1e9,l,r)-query(rt[dfn[x]-1],1,1e9,l,r));
    }
    return 0;
}

P1383 高级打字机
思路:还是一道主席树的板子题,题目比较清楚的告诉了这题需要访问历史版本,因此需要可持久化数据结构,因此用主席树。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll ;
typedef pair<int,int> PII;
typedef pair<ll,ll> PLL;
#define debug() cout<<"fuck      "<<endl
#define fr(i,k,n) for(int i=k;i<=n;i++)
#define fo(i,k,n) for(int i=n;i>=k;i--)
const int mod = 998244353,N=1e5+5,INF=0x3f3f3f3f;
const double eps=1e-6;
ll n,m,k,ans=0,pos=0,sz=0;
int rt[N<<5],lc[N<<5],rc[N<<5],len[N<<5];
char s[5],ch,val[N<<5];
struct node
{
    int l,r;
    char ch;
}b[N*4];
inline int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}
    while(ch>='0'&& ch<='9') {x=x*10+ch-'0';ch=getchar();}
    return f*x;
}
void update(int&u,int v,int l,int r,char ch)
{
     u=++sz;
     lc[u]=lc[v],rc[u]=rc[v];
     if(l==r)
     {
         val[u]=ch;
         //cout<<u<<" "<<val[u]<<endl;
         return ;
     }
     int mid=(l+r)>>1;
     if(len[pos]<=mid) update(lc[u],lc[v],l,mid,ch);
     else update(rc[u],rc[v],mid+1,r,ch);
}
char query(int u,int l,int r,int pos)
{
    if(l==r)
        return val[u];
    int mid=(l+r)>>1;
    if(pos<=mid) return query(lc[u],l,mid,pos);
    else return query(rc[u],mid+1,r,pos);
}
int main()
{
    std::ios::sync_with_stdio(false);
    cin.tie(0);
    cin>>n;
    fr(i,1,n)
    {
        int k;
        cin>>s;
        if(s[0]=='T')
        {
           cin>>ch;
           ++pos;
           len[pos]=len[pos-1]+1;  
           update(rt[pos],rt[pos-1],1,n,ch);
        }
        else
        {
            cin>>k;
            if(s[0]=='U')
            {
                ++pos;
                rt[pos]=rt[pos-k-1];
                len[pos]=len[pos-k-1];
            }
            else
                cout<<query(rt[pos],1,n,k)<<endl;
        }
    }
    return 0;
}

P2633 Count on a tree
思路:LCA+主席树+树上差分
u->v路径上的节点数:sum[rt[u]]+sum[rt[v]]-sum[LCA]-sum[fa[LCA]]
主席树记录节点u到祖先节点路径上各个点的权值。
板子都很熟悉,只不过思路一直跳不开怎么去创建只有祖先节点的主席树,那么就在建树的时候同时更新就行了。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll ;
typedef pair<int,int> PII;
typedef pair<ll,ll> PLL;
#define debug() cout<<"fuck      "<<endl;
#define fr(i,k,n) for(int i=k;i<=n;i++)
#define fo(i,k,n) for(int i=n;i>=k;i--)
const int mod = 998244353,N=1.2e5+5,INF=1e9;
const double eps=1e-6;
int n,m,sz=0,p,q,t=30,last=0,ans=0;
int rt[N<<5],lc[N<<5],rc[N<<5],sum[N<<5];
int a[N],b[N],fa[N][32],d[N];
char s[5];
vector<int>h[N];
inline int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}
    while(ch>='0'&& ch<='9') {x=x*10+ch-'0';ch=getchar();}
    return f*x;
}
int update(int o,int l,int r,int p)
{
    int oo=++sz;
    lc[oo]=lc[o],rc[oo]=rc[o];sum[oo]=sum[o]+1;
    if(l==r)
        return oo;
    int mid=(l+r)>>1;
    if(p<=mid) lc[oo]=update(lc[oo],l,mid,p);
    else rc[oo]=update(rc[oo],mid+1,r,p);
    return oo;
}
int query(int u,int v,int l,int r,int k,int lca,int gra)
{
    int x=sum[lc[v]]+sum[lc[u]]-sum[lc[lca]]-sum[lc[gra]];
    if(l==r)
        return l;
    int mid=(l+r)>>1;
    if(k<=x) return query(lc[u],lc[v],l,mid,k,lc[lca],lc[gra]);
    else return query(rc[u],rc[v],mid+1,r,k-x,rc[lca],rc[gra]);
}
void dfs(int x,int f)
{
    d[x]=d[f]+1;
    for(int i=0;i<h[x].size();i++)
    {
        int y=h[x][i];
        if(y==f) continue;
        fa[y][0]=x;
        rt[y]=update(rt[x],1,q,a[y]);
        dfs(y,x);
    }
}
int LCA(int x,int y)
{
    if(d[x]>d[y]) swap(x,y);
    for(int j=t;j>=0;j--)
        if(d[fa[y][j]]>=d[x]) y=fa[y][j];
    if(x==y) return x;
    for(int j=t;j>=0;j--)
    if(fa[x][j]!=fa[y][j])
        x=fa[x][j],y=fa[y][j];
    return fa[x][0];
}
int main()
{
   n=read(),m=read();
   t=log2(n)+1;
   fr(i,1,n) a[i]=read(),b[i]=a[i];
   sort(b+1,b+n+1);
   q=unique(b+1,b+n+1)-b-1;
   fr(i,1,n) a[i]=lower_bound(b+1,b+q+1,a[i])-b;
   fr(i,1,n-1)
   {
       int x=read(),y=read();
       h[x].push_back(y);
       h[y].push_back(x);
   }
   rt[1]=update(rt[0],1,q,a[1]);
   dfs(1,0);
   for(int i=1;i<=t;i++)  ///倍增祖父节点
   for(int j=1;j<=n;j++)
     fa[j][i]=fa[fa[j][i-1]][i-1];
   fr(i,1,m)
   {
       int l=read()^last,r=read(),k=read();
       int lca=LCA(l,r);
       ans=b[query(rt[l],rt[r],1,q,k,rt[lca],rt[fa[lca][0]])];
       printf("%d\n",ans);
       last=ans;
   }
   return 0;
}

too the moon
思路:题目要求访问历史版本,所以利用主席树写。一个带修的主席树模板。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll ;
typedef pair<int,int> PII;
typedef pair<ll,ll> PLL;
#define debug() cout<<"fuck      "<<endl
#define fr(i,k,n) for(int i=k;i<=n;i++)
#define fo(i,k,n) for(int i=n;i>=k;i--)
const int mod = 998244353,N=1e5+5,INF=0x3f3f3f3f;
const double eps=1e-6;
int n,m,k,ans=0,q,sz=0,now=0;
int rt[N<<5],lc[N<<5],rc[N<<5],a[N];
ll sum[N<<5],add[N<<5];
char s[5];
void build(int &rt,int l,int r)
{
    rt=++sz;
    add[sz]=0;
    if(l==r)
    {
       sum[rt]=a[l];
       return ;
    }
    int mid=(l+r)>>1;
    build(lc[rt],l,mid);
    build(rc[rt],mid+1,r);
    sum[rt]=sum[lc[rt]]+sum[rc[rt]];
}
int update(int o,int l,int r,int L,int R,ll k)
{
    int oo=++sz;
    lc[oo]=lc[o],rc[oo]=rc[o],sum[oo]=sum[o],add[oo]=add[o];
    sum[oo]+=1ll*(R-L+1)*k;
    if(L<=l&&r<=R)
    {
       add[oo]+=k;
       return oo;
    }
    int mid=(l+r)>>1;
    if(R<=mid) lc[oo]=update(lc[oo],l,mid,L,R,k);
    else if(L>mid) rc[oo]=update(rc[oo],mid+1,r,L,R,k);
    else
    {
        lc[oo]=update(lc[oo],l,mid,L,mid,k);
        rc[oo]=update(rc[oo],mid+1,r,mid+1,R,k);
    }
    return oo;
}
ll query(int u,int l,int r,int L,int R)
{
    if(L<=l&&r<=R)
        return sum[u];
    int mid=(l+r)>>1;
    ll ans=add[u]*1ll*(R-L+1);
    if(R<=mid) ans+=query(lc[u],l,mid,L,R);
    else if(L>mid) ans+=query(rc[u],mid+1,r,L,R);
    else
    {
         ans+=query(lc[u],l,mid,L,mid);
         ans+=query(rc[u],mid+1,r,mid+1,R);
    }
    return ans;
}
int main()
{
    std::ios::sync_with_stdio(false);
    cin.tie(0);
    while(cin>>n>>m)
    {
        sz=0;now=0;
        fr(i,1,n) cin>>a[i];
        build(rt[0],1,n);
        fr(i,1,m)
        {
            int l,r,t;
            ll k;
            cin>>s;
            if(s[0]=='B')
            {
                cin>>t;
                now=t;
            }
            else
            {
                cin>>l>>r;
                if(s[0]=='C')
                {
                    cin>>k;
                    ++now;
                    rt[now]=update(rt[now-1],1,n,l,r,k);
                }
                if(s[0]=='H')
                {
                    cin>>t;
                    cout<<query(rt[t],1,n,l,r)<<endl;
                }
                if(s[0]=='Q')
                    cout<<query(rt[now],1,n,l,r)<<endl;
            }
        }
    }
    return 0;
}

P2617 Dynamic Rankings
思路:树状数组套主席树。如果单次修改[l,n]所有的主席树时间复杂度将是O(nlogn),会TLE。因此选择修改O(logn)个主席树,主席树思想就是前缀和,因此利用树状数组修改logn个主席树,并对[l-1,r] 的树查询。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll ;
typedef pair<int,int> PII;
typedef pair<ll,ll> PLL;
#define debug() cout<<"fuck      "<<endl
#define fr(i,k,n) for(int i=k;i<=n;i++)
#define fo(i,k,n) for(int i=n;i>=k;i--)
const int mod = 998244353,N=4e5+5,INF=0x3f3f3f3f;
const double eps=1e-6;
int n,m,q,sz=0,n1,n2,tot;
int rt[N],lc[N<<5],rc[N<<5],val[N<<5];
int a[N],b[N<<1],t1[N],t2[N];
char s[5],ch;
struct node
{
    int l,r,k;
}que[N];
int lowbit(int x) {return x&-x;}
void add(int &rt,int l,int r,int pos,int k)
{
    if(!rt) rt=++sz;
    val[rt]+=k;
    if(l==r) return ;
    int mid=(l+r)>>1;
    if(pos<=mid) add(lc[rt],l,mid,pos,k);
    else add(rc[rt],mid+1,r,pos,k);
}
void change(int pos,int k)
{
   int x=lower_bound(b+1,b+q+1,a[pos])-b;
   for(int i=pos;i<=n;i+=lowbit(i))  ///修改logn个主席树
     add(rt[i],1,q,x,k);
}
int Kth(int l,int r,int k)  ///查询[l,r] 第k大的数
{
    if(l==r) return l;
    int mid=(l+r)>>1,sum=0;
    fr(i,1,n2) sum+=val[lc[t2[i]]];
    fr(i,1,n1) sum-=val[lc[t1[i]]];
    if(sum>=k)
    {
        fr(i,1,n1) t1[i]=lc[t1[i]];
        fr(i,1,n2) t2[i]=lc[t2[i]];
        return Kth(l,mid,k);
    }
    else
    {
        fr(i,1,n1) t1[i]=rc[t1[i]];
        fr(i,1,n2) t2[i]=rc[t2[i]];
        return Kth(mid+1,r,k-sum);
    }
}
int Kth_pre(int l,int r,int k)
{
    n1=n2=0;
    for(int i=l-1;i>=1;i-=lowbit(i)) ///树状数组查询logn个主席树
        t1[++n1]=rt[i];
    for(int i=r;i>=1;i-=lowbit(i))
        t2[++n2]=rt[i];
    return Kth(1,q,k);
}
int main()
{
    int T=1;
    while(T--)
    {
        cin>>n>>m;
        sz=0;tot=n;
        memset(rt,0,sizeof(rt));
        memset(lc,0,sizeof(lc));
        memset(rc,0,sizeof(rc));
        memset(val,0,sizeof(val));
        fr(i,1,n) cin>>a[i],b[i]=a[i];
        memset(que,0,sizeof(que));
        fr(i,1,m)
        {
            cin>>ch;
            if(ch=='C')
            {
                cin>>que[i].l>>que[i].r;
                b[++tot]=que[i].r;
            }
            if(ch=='Q')
                cin>>que[i].l>>que[i].r>>que[i].k;
        }
        sort(b+1,b+tot+1);
        q=unique(b+1,b+tot+1)-b-1;
        fr(i,1,n) change(i,1);
        fr(i,1,m)
        {
            if(que[i].k)
              cout<<b[Kth_pre(que[i].l,que[i].r,que[i].k)]<<endl;
            else
            {
                change(que[i].l,-1);
                a[que[i].l]=que[i].r;
                change(que[i].l,1);
            }
        }
    }
    return 0;
}

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值