问题
给出n个数,q个询问,求l-r内的第k小值(n,q<=2e5)
方法一:平衡树
方法二:主席树
下面来看一看主席树是怎么做的。
主席树是一种特殊的线段树,针对这一题,我们可以对每个区间[x,y]维护一颗线段树,[l,r]表示在[x,y]区间内,数的大小在[l,r]范围内的数的个数。
但这种思想的实现一定超过了空间与时间的限制。
- 考虑优化时间。考虑前缀和的思想,首先离散化数据,sum[1,i]-sum[1,j-1]即代表了区间sum[j,i],这里不妨也可以运用这种思想,只对每一个前缀进行维护。
- 但光有上述这点还是不够的。我们会发现,对于sum[1,i]和sum[1,i+1]这两个状态,只有一个元素的差异,所以可以考虑持久化。
即对sum[1,i+1]的[l,r]来说,若[l,mid]和sum[1,i]是相同的,则可以直接指向sum[1,i]的[l,mid],若是不相同的,则新建一个节点。很容易发现,这样的空间复杂度是logn*n的。
补充1:对于2,有其一定的实现技巧。
//此处的insert指插入x节点
procedure insert(pre,x,h,t:longint); var tmp,mid:longint; begin inc(now); p[now]:=p[pre]; inc(p[now].x); tmp:=now; if h=t then exit; mid:=(h+t) div 2; if (x<=mid ) then begin insert(p[now].h,x,h,mid); p[tmp].h:=tmp+1; end else begin insert(p[now].t,x,mid+1,t); p[tmp].t:=tmp+1; end; end;
补充2:对于query操作,考虑左边数的个数>=sum,则往左边走,反之往右走。但此处由于前缀和操作的存在增加了一定的复杂性。其实也就是要同时统计出两个区间1-x,1-y的数值的当前范围。
function query(x,y,h,t,sum:longint):longint; var tmp,mid:longint; begin if (h=t) then exit(h); tmp:=p[p[y].h].x-p[p[x].h].x; mid:=(h+t) div 2; if tmp<sum then exit(query(p[x].t,p[y].t,mid+1,t,sum-tmp)) else exit(query(p[x].h,p[y].h,h,mid,sum)); end;
以上的是离线的主席树,时间复杂度是o(nlogn) 空间(nlogn);
完整代码:
uses math; type re=record a,b,c:longint; end; type ree=record x,h,t:longint; end; var i,j,m,n,now,l,c,d,e,tmp:longint; a:array[0..202222]of re; num,f,real:array[0..222222]of longint; p:array[0..10002222]of ree; procedure swap(var x,y:re); var tmp:re; begin tmp:=x; x:=y; y:=tmp; end; procedure qsort(h,t:longint); var i,j,mid:longint; begin i:=h; j:=t; mid:=a[(i+j) div 2].a; repeat while a[i].a<mid do inc(i); while a[j].a>mid do dec(j); if i<=j then begin swap(a[i],a[j]); inc(i); dec(j); end; until i>j; if i<t then qsort(i,t); if h<j then qsort(h,j); end; procedure build(x,h,t:longint); var mid:longint; begin p[x].h:=x*2; p[x].t:=x*2+1; now:=max(now,x*2+1); if h=t then exit; mid:=(h+t) div 2; build(x*2,h,mid); build(x*2+1,mid+1,t); end; procedure insert(pre,x,h,t:longint); var tmp,mid:longint; begin inc(now); p[now]:=p[pre]; inc(p[now].x); tmp:=now; if h=t then exit; mid:=(h+t) div 2; if (x<=mid ) then begin insert(p[now].h,x,h,mid); p[tmp].h:=tmp+1; end else begin insert(p[now].t,x,mid+1,t); p[tmp].t:=tmp+1; end; end; function query(x,y,h,t,sum:longint):longint; var tmp,mid:longint; begin if (h=t) then exit(h); tmp:=p[p[y].h].x-p[p[x].h].x; mid:=(h+t) div 2; if tmp<sum then exit(query(p[x].t,p[y].t,mid+1,t,sum-tmp)) else exit(query(p[x].h,p[y].h,h,mid,sum)); end; begin readln(n,m); for i:=1 to n do begin read(a[i].a); a[i].b:=i; end; qsort(1,n); a[0].a:=-maxlongint; l:=0; for i:=1 to n do begin if a[i].a<>a[i-1].a then inc(l); num[a[i].b]:=l; real[l]:=a[i].a; end; build(1,1,n); f[0]:=1; for i:=1 to n do begin f[i]:=now+1; insert(f[i-1],num[i],1,n); end; for i:=1 to m do begin read(c,d,e); writeln(real[query(f[c-1],f[d],1,n,e)]); end; end.
#include<bits/stdc++.h> using namespace std; const int maxn=1000000; struct re{int a,b;}; struct ree{int h,t,x;}; re a[maxn]; ree p[maxn*20]; int n,m,l,now,rea[maxn],num[maxn],f[maxn]; void build(int x,int h,int t) { p[x].h=x*2; p[x].t=x*2+1; now=max(now,x*2+1); if (h==t) return; int mid=(h+t)/2; build(x*2,h,mid); build(x*2+1,mid+1,t); }; void insert(int pre,int x,int h,int t) { now++; p[now]=p[pre]; p[now].x++; if (h==t) return; int mid=(h+t)/2; if (x<=mid) p[now].h=now+1,insert(p[pre].h,x,h,mid); else p[now].t=now+1,insert(p[pre].t,x,mid+1,t); }; int query(int x,int y,int h,int t,int sum) { if (h==t) return(h); int tmp=p[p[y].h].x-p[p[x].h].x; int mid=(h+t)/2; if (tmp<sum) return(query(p[x].t,p[y].t,mid+1,t,sum-tmp)); else return(query(p[x].h,p[y].h,h,mid,sum)); }; bool cmp(re x,re y) { return(x.a<y.a); }; int main(){ cin>>n>>m; for (int i=1;i<=n;i++) cin>>a[i].a,a[i].b=i; sort(a+1,a+n+1,cmp); a[0].a=-500000000; for (int i=1;i<=n;i++) { if (a[i].a!=a[i-1].a) l++; num[a[i].b]=l; rea[l]=a[i].a; } build(1,1,n); f[0]=1; for (int i=1;i<=n;i++) { f[i]=now+1; insert(f[i-1],num[i],1,n); } for (int i=1; i<=m;i++) { int c,d,e; cin>>c>>d>>e; cout<<(rea[query(f[c-1],f[d],1,n,e)])<<endl; } }
总的来说,主席树是前缀和+动态开点的线段树
接下来的是在线版的主席树,要求支持区间的修改。
这种算法保留了主席树中的权值线段树和动态开点的思想
同时加入了树状数组维护(因此没有了前缀和维护)
即对于每一个节点开一颗权值线段树,查询和修改时类似树状数组的思想
时间复杂度(nlognlogn) 空间复杂度(nlognlogn)
来源:https://www.cnblogs.com/yinwuxiao/p/7892787.html