【線段樹】樹套樹 樹狀陣列套主席樹
樹狀陣列套主席樹
(其實是樹狀陣列套動態開點權值線段樹
作用:
即動態主席樹,動態(帶修)查詢區間 \(k\) 小(大)值
做法:
回顧主席樹:簡單來說主席樹在查詢時 是查詢 \(T[r]-T[l-1]\) 區間的值的,建立是 \(T[i+1]\) 在 \(T[i]\) 的基礎上建立的。用於靜態查詢區間 \(k\) 小(大)值。
而帶修主席樹,因為修改,所以在 \(T[i]\) 基礎上建 \(T[i+1]\) 並不合適。
主席樹的思想是減去區間的字首和求差,此時帶修主席樹依舊是考慮維護字首和,然而因為帶修,所以主席樹各版本之間的強關聯性並不適合修改,此時再建立主席樹不再需要對線段樹進行可持久化,只需動態開點建權值線段樹。
考慮用樹狀陣列維護字首和,及對樹狀陣列每個 \(lowbit\) 的點( \(logn\) 個)建立動態開點權值線段樹,
在修改陣列 \(x\) 位置時,則是對樹狀陣列 \(for( ;x<=n;x+=lowbit(x))\) 上各點的權值線段樹進行修改;
在查詢陣列區間 \([l,r]\) 第 \(k\) 小(大)值時,則是對樹狀陣列 \(for(i=r;i;i-=lowbit(i))\) 以及 \(for(i=l-1;i;i-=lowbit(i))\) 線上段樹當前區間的相應各點上的值差 進行線段樹的二分查詢(詳見模板)。
例題:
模板題,帶修查詢區間k小值:
給定一個含有 \(n\) 個數的序列 \(a_1,a_2 \dots a_n\),有 \(m\) 個操作,需要支援兩種操作:
Q l r k
表示查詢下標在區間 \([l,r]\) 中的第 \(k\) 小的數C x y
表示將 \(a_x\) 改為 \(y\)
\(1\le n,m \le 10^5\), \(1 \le l \le r \le n\) ,\(1 \le k \le r-l+1\) ,\(1\le x \le n\) ,\(0 \le a_i,y \le 10^9\) 。
\(code:\)
#include<bits/stdc++.h> #define mem(a,b) memset(a,b,sizeof(a)) using namespace std; typedef long long ll; const int mod=1e9+9; const int inf=0x3f3f3f3f; const int maxn=2e5+5; const double ep=1e-6; void read(int&x) { char c; while(!isdigit(c=getchar()));x=c-'0'; while(isdigit(c=getchar()))x=(x<<3)+(x<<1)+c-'0'; } //P2617 int n,cnt; int h[maxn]; inline int id(int x){ return lower_bound(h+1,h+1+cnt,x)-h; } struct Q{ int a,b,c; }q[maxn]; //樹狀陣列套主席樹(其實是套動態開點權值線段樹 //對樹狀陣列每個節點建權值線段樹 struct Tr{ // <<8 是因為log*log // maxn=2e5是因為權值包括陣列的n個權值 以及q的m個權值 maxn=n+m int T[maxn],L[maxn<<8],R[maxn<<8],sum[maxn<<8],tot=0; void supdate(int &rt,int l,int r,int x,int v)//線段樹 { if(!rt)rt=++tot; sum[rt]+=v; if(l==r)return; int mid=(l+r)>>1; if(x<=mid)supdate(L[rt],l,mid,x,v); else supdate(R[rt],mid+1,r,x,v); } void update(int x,int vx,int v)//樹狀陣列 x是陣列位置,vx是權值位置 v是+1 ,-1這樣子 { for(;x<=n;x+=x&-x)supdate(T[x],1,cnt,vx,v); } //求區間第k小 int qr[maxn],ql[maxn],cntl,cntr; void pre_query(int x,int q[],int&cnt){ for(cnt=0;x;x-=x&-x)q[++cnt]=T[x]; } //l,r是二分線權值段樹 k是區間第k小的k //l和r是權值區間,陣列區間是在樹狀陣列上分的 就是lowbit部分那裡處理判斷陣列區間 int query(int l,int r,int k)//qury(T[qR],T[qL-1],l,r,k) 提前記錄log個節點T[qR]及T[qL-1]在q[]處 { if(l==r)return l; int mid=(l+r)>>1,num=0; for(int i=1;i<=cntr;i++)num+=sum[L[qr[i]]]; for(int i=1;i<=cntl;i++)num-=sum[L[ql[i]]];//其實就是num=L[rt]-L[pre] if(num>=k) { for(int i=1;i<=cntr;i++)qr[i]=L[qr[i]]; for(int i=1;i<=cntl;i++)ql[i]=L[ql[i]]; return query(l,mid,k);//相當於 query(L[rt],L[pre],l,mid,k); 因為要記錄樹狀陣列節點 所以得提前記錄 }else{ for(int i=1;i<=cntr;i++)qr[i]=R[qr[i]]; for(int i=1;i<=cntl;i++)ql[i]=R[ql[i]]; return query(mid+1,r,k-num);//query(R[rt],R[pre],mid+1,r,k-num); } } }tr; int a[maxn]; char op[2]; int main() { int m; read(n);read(m); for(int i=1;i<=n;i++)read(a[i]),h[++cnt]=a[i]; int l,r,k,x,y; for(int i=1;i<=m;i++) { scanf("%s",op); if(op[0]=='Q'){ read(l);read(r);read(k); q[i]={l,r,k}; }else{ read(x);read(y);h[++cnt]=y; q[i]={x,y,0}; } } sort(h+1,h+1+cnt); cnt=unique(h+1,h+1+cnt)-h-1; for(int i=1;i<=n;i++)tr.update(i,id(a[i]),1); for(int i=1;i<=m;i++) { if(q[i].c){ l=q[i].a;r=q[i].b;k=q[i].c; tr.pre_query(r,tr.qr,tr.cntr); tr.pre_query(l-1,tr.ql,tr.cntl); printf("%d\n",h[tr.query(1,cnt,k)]); }else{ x=q[i].a;y=q[i].b; tr.update(x,id(a[x]),-1);a[x]=y; tr.update(x,id(a[x]),1); } } }