【树状数组套主席树】带修改区间K大数
?
一些奇怪的东西 这个大概是luogu某题目,然而我太菜了,所以现在才会做。 这个题目显然是由一些静态的东西衍生而来的(没错就是静态区间K大数) Hmm...昨天(可能是今天??)有人问我,一些静态和动态的问题,我列举一下: 静态区间K大数(排个序就好了) 静态区间K大数(主席树搞一搞) 待修改区间K大数(我就要讲的这个嘛) 动态区间和(zkw线段树/朴素线段树/2个树状数组) 静态区间和(...前缀和?) 步入正题 话说带修改区间K大数(注意是修改而并不是插入) 显然我们如果按照静态区间K大数的方法搞,暴力更新整棵线段树那么复杂度将是O(n log2 n)修改每次 想到单点修改求前缀和想到树状数组。不妨用树状数组维护线段树每个节点的前缀和, 换句话说要想求得每个节点具体的值,那么必须将属于这个节点的log2 n个节点的和全部累加,才是这个节点值域范围内,前缀插入数的个数 既然我们能求出在线段树某一节点值域范围内,前缀插入数的个数,我们就按照和静态区间K大数的类二分查找(用线段树对值域的二分代替整体二分)来找到一个对于的树根,输出他的标号就行。 所以,树状数组是维护一个数组表示的是要想知道当前每一节点值域是[L,R]前缀插入数的个数,是哪log2 n个节点的累加和。 这样子复杂度是O(log22n)每次插入。 查询的时候也是这样用R的前缀插入数的个数减去(l-1)前缀插入数的个数,就是该节点值域在[L,R]区间内插入数的个数。 这样的复杂度也是O(log22n)每次查询。 注意一些细节: 记录那几个点的数组(node_add和node_cut只需开log2n个即可), 然后离线离散化以后在线做(其实和在线效果一样), 然后离散化的时候尽量不用vector(不好习惯) 对tmp[]离散化,T记录离散化后下标 sort(tmp+1,tmp+1+tmp[0]); T=unique(tmp+1,tmp+1+tmp[0])-tmp; 若要查询某个数val离散化以后是多少,那么就是 w=lower_bound(tmp+1,tmp+1+T,val)-tmp; 若要查询某个离散化后的数kkk实际上是多少那么直接访问下标 w=o[kkk]; 还是得解释代码: # include <cstdio> # include <map> # include <algorithm> # include <vector> # include <cstring> using namespace std; const int N=1e5+10; # define lowbit(x) (x&(-x)) # define lson t[rt].ls,l,mid # define rson t[rt].rs,mid+1,r # define mid ((l+r)>>1) int tmp[N<<1]; struct rec{ int l,r,k,o; }qes[N]; struct Seqment_Tree{ int ls,rs,val; }t[N*400];//树套树空间得开 n log n int node_cut[25],node_add[25]; //这里只要log n个就行后面memset会慢 int cnt_cut,cnt_add,tot; int root[N],a[N]; int T,n,m; inline int read() { int X=0,w=0; char c=0; while(c<‘0‘||c>‘9‘) {w|=c==‘-‘;c=getchar();} while(c>=‘0‘&&c<=‘9‘) X=(X<<3)+(X<<1)+(c^48),c=getchar(); return w?-X:X; } void write(int x) { if (x<0) x=-x,putchar(‘-‘); if (x>9) write(x/10); putchar(‘0‘+x%10); }//I/O优化 void update(int &rt,int l,int r,int pos,int val) { if (!rt) rt=++tot; //如果此时的访问空的节点那么建立节点,否则会覆盖掉原有节点信息(普通主席树最好也这么写,但是没必要,因为每个节点一定是空的!!!) t[rt].val+=val; if (l==r) return; if (pos<=mid) update(lson,pos,val); else update(rson,val); }//普通主席树的更改维护,值域+1/-1 void pre_update(int x,int val) { int w=lower_bound(tmp+1,a[x])-tmp; for (int i=x;i<=n;i+=lowbit(i)) update(root[i],1,T,w,val); }//首先处理出那几棵线段树管这个数组的位置的前缀和的,然后每个线段树分别维护 int query(int l,int k) { if (l==r) return l; int ret=0; for (int i=1;i<=cnt_add;i++) ret+=t[t[node_add[i]].ls].val; for (int i=1;i<=cnt_cut;i++) ret-=t[t[node_cut[i]].ls].val; //该加的加(r),该减的减(l-1) if (k<=ret) { for (int i=1;i<=cnt_add;i++) node_add[i]=t[node_add[i]].ls; for (int i=1;i<=cnt_cut;i++) node_cut[i]=t[node_cut[i]].ls; return query(l,mid,k); //不足右边不可能往左边搜 } else { for (int i=1;i<=cnt_add;i++) node_add[i]=t[node_add[i]].rs; for (int i=1;i<=cnt_cut;i++) node_cut[i]=t[node_cut[i]].rs; return query(mid+1,k-ret); //超过左边不可能往右边搜 } } int pre_query(int l,int k) { memset(node_add,0,sizeof(node_add)); memset(node_cut,sizeof(node_cut)); cnt_add=cnt_cut=0;//清空 for (int i=r;i;i-=lowbit(i)) node_add[++cnt_add]=root[i]; //处理该加的根节点 for (int i=l-1;i;i-=lowbit(i)) node_cut[++cnt_cut]=root[i]; //处理该减的根节点 return query(1,k); } int main() { n=read();m=read(); for (int i=1;i<=n;i++) tmp[++tmp[0]]=a[i]=read(); for (int i=1;i<=m;i++) { char ch=0; while (ch!=‘Q‘&&ch!=‘C‘) ch=getchar(); if (ch==‘Q‘) qes[i].l=read(),qes[i].r=read(),qes[i].k=read(),qes[i].o=1; else qes[i].l=read(),tmp[++tmp[0]]=qes[i].r=read(),qes[i].o=0; } sort(tmp+1,tmp+1+tmp[0])-tmp; for (int i=1;i<=n;i++) pre_update(i,1); for (int i=1;i<=m;i++) { if (qes[i].o==1) { write(tmp[pre_query(qes[i].l,qes[i].r,qes[i].k)]); putchar(‘n‘); } else { pre_update(qes[i].l,-1); a[qes[i].l]=qes[i].r; pre_update(qes[i].l,1); } } return 0; } (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |