主席樹-區間第k大值(不帶修改)
阿新 • • 發佈:2019-02-19
題意:求區間第K大的值。
分析:資料1
主席樹包含n棵線段樹,這n棵線段樹的形狀完全相同。而且樹與樹之間有很大的重疊。
線段樹root[i]表示陣列a中區間[1,i]的元素插進線段樹時的版本。
那麼再新增一個元素a[i+1]時,只需修改線段樹上的從根節點開始向下走的一條路徑。
那麼對於線段樹root[i+1],我們先“拷貝”線段樹root[i]的所有資訊,然後只“修改”那一條路徑就好了。
修改的話,不能真的修改,因為線段樹root[i+1]用的是的root[i]的節點。我們線上段樹root[i+1]上重新新增一條路徑
來覆蓋之前的路徑,而線段樹root[i]得以儲存自身版本的資訊。
用root[r]-root[l-1]就可以表示區間[l,r]的資訊了。
程式碼:
#include <iostream> #include <algorithm> using namespace std; typedef long long LL; typedef unsigned long long ULL; const LL INF = 1E9+9; const int maxn = 100006; const int LOG = 20; struct node { int l,r; int sum; }Node[maxn*LOG]; int root[maxn],node_cnt; int numbers[maxn],num_cnt; int a[maxn]; void build(int l,int r,int &rt) { rt=++node_cnt; Node[rt].l=Node[rt].r=Node[rt].sum=0; if(l==r) return ; int m=(l+r)>>1; build(l,m,Node[rt].l); build(m+1,r,Node[rt].r); } void update(int v,int l,int r,int &rt,int pre) { rt=++node_cnt; Node[rt]=Node[pre]; ++Node[rt].sum; if(l==r) return ; int m=(l+r)>>1; if(v<=m) update(v,l,m,Node[rt].l,Node[pre].l); else update(v,m+1,r,Node[rt].r,Node[pre].r); } int query(int k,int l,int r,int r1,int r2) { if(l==r) return r; int lnum=Node[Node[r2].l].sum-Node[Node[r1].l].sum; int m=(l+r)>>1; if(k<=lnum) return query(k,l,m,Node[r1].l,Node[r2].l); else return query(k-lnum,m+1,r,Node[r1].r,Node[r2].r); } int main() { // ios::sync_with_stdio(false); int n,m; int ncase; scanf("%d",&ncase); while(ncase--) { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) { scanf("%d",&a[i]); // cin>>a[i]; numbers[i]=a[i]; } sort(numbers+1,numbers+n+1); num_cnt=unique(numbers+1,numbers+n+1)-numbers-1; node_cnt=0; root[0]=0; build(1,num_cnt,root[0]); for(int i=1;i<=n;i++) { int pos = lower_bound(numbers+1,numbers+num_cnt+1,a[i])-numbers; update(pos,1,num_cnt,root[i],root[i-1]); } while(m--) { int L,R,K; cin>>L>>R>>K; int q=query(K,1,num_cnt,root[L-1],root[R]); printf("%d\n",numbers[q]); // cout<<numbers[q]<<'\n'; } } return 0; }