标签:基础上 int 数组 树状数组 scanf 线段 前缀 操作 max
话说主席树还没写就先写这一篇了\(qwq\)
回顾一下主席树的实现过程:类似查分思想,将线段树的每次修改看做函数式以支持可持久化。因为这样的线段树是可减的。
那么我们维护信息的时候,就要维护每一次新形成的信息。但是我们可以根据前一个信息的基础上进行改动,而不必要去再建一棵树。
所以总而言之,是前缀和的思想。
那么,当需要修改的时候,怎么做呢?
考虑普通的区间操作,当做单点修改的时候,一般用树状数组,线段树和分块。最好实现的就是树状数组。
考虑用树状数组来维护主席树的信息。
树状数组中维护了每一次加入一个数的新形成的主席树的信息。
对于修改一个值,考虑树状数组的单点修改,若修改点\(p\),则把一路上的\(p+lowbit(p)\)全部修改即可,这样是\(O(log)\)的,主席树修改是\(O(log)\)的,即总复杂度\((\log^2{n})\).
查询的时候,我们可以用树状数组提前将第\(r\)棵树和第\(l-1\)棵树的信息预处理出来,是\(log\)的,然后在树上二分,跳左右子树的时候,这\(log\)个信息也一起跳。大概复杂度也是\(O(\log^2{n})\)的。
总时间复杂度:\(O(n\log^2{n})\)
代码:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e6+10;
int n,m,a[MAXN],u[MAXN],x[MAXN];
int l[MAXN],r[MAXN],k[MAXN],cur;
int cur1,cur2,q1[MAXN],q2[MAXN],v[MAXN];
char op[MAXN];
set<int>ST;
map<int,int>mp;
struct SGT{
int cur,rt[MAXN<<2],sum[MAXN<<5],lc[MAXN<<5],rc[MAXN<<5];
void build(int &o){o=++cur;}
void print(int o,int l,int r){
if(!o)return;
if(l==r&&sum[o])printf("%d",l);
int mid=l+r>>1;
print(lc[o],l,mid);
print(rc[o],mid+1,r);
}
void update(int &o,int l,int r,int x,int v){
if(!o)o=++cur;
sum[o]+=v;
if(l==r)return ;
int mid=l+r>>1;
if(x<=mid)update(lc[o],l,mid,x,v);
else update(rc[o],mid+1,r,x,v);
}
}st;
inline int lbt(int x){return x&(-x);}
void upd(int o,int x,int v){
for(;o<=n;o+=lbt(o))st.update(st.rt[o],1,n,x,v);
}
void gtv(int o,int *A,int &p){
p=0;
//A数组维护了树状数组o所控制的所有根信息
for(;o;o-=lbt(o))A[++p]=st.rt[o];
}
int query(int l,int r,int k){
if(l==r)return l;
int mid=l+r>>1,siz=0;
for(int i=1;i<=cur1;++i)siz+=st.sum[st.lc[q1[i]]];
//q1是r树的信息
for(int i=1;i<=cur2;++i)siz-=st.sum[st.lc[q2[i]]];
//q2是l-1树的信息,通过gtv处理
//siz代表信息的区间[l,r]信息
if(siz>=k){
for(int i=1;i<=cur1;++i)q1[i]=st.lc[q1[i]];
for(int i=1;i<=cur2;++i)q2[i]=st.lc[q2[i]];
return query(l,mid,k);//q1q2一样改,走左子树
}
else{
for(int i=1;i<=cur1;++i)q1[i]=st.rc[q1[i]];
for(int i=1;i<=cur2;++i)q2[i]=st.rc[q2[i]];
return query(mid+1,r,k-siz);
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)scanf("%d",a+i),ST.insert(a[i]);
for(int i=1;i<=m;++i){
scanf(" %c",op+i);
if(op[i]=='C')scanf("%d%d",u+i,x+i),ST.insert(x[i]);
else scanf("%d%d%d",l+i,r+i,k+i);
}
for(set<int>::iterator it=ST.begin();it!=ST.end();++it)
mp[*it]=++cur,v[cur]=*it;//unique
for(int i=1;i<=n;++i)a[i]=mp[a[i]];
for(int i=1;i<=m;++i)if(op[i]=='C')x[i]=mp[x[i]];
n+=m;
for(int i=1;i<=n;++i)upd(i,a[i],1);
for(int i=1;i<=m;++i){
if(op[i]=='C'){
upd(u[i],a[u[i]],-1);
upd(u[i],x[i],1);
a[u[i]]=x[i];
}
else{
gtv(r[i],q1,cur1);//预处理
gtv(l[i]-1,q2,cur2);//预处理
printf("%d\n",v[query(1,n,k[i])]);
}
}
return 0;
}
代码中的\(set\)是用来去重的,\(map\)是用来离散化的。
注意询问的字符输入,不要漏了前面的空格。
标签:基础上 int 数组 树状数组 scanf 线段 前缀 操作 max
原文地址:https://www.cnblogs.com/h-lka/p/12294168.html