I Concept of weighted segment tree
The weighted segment tree is a segment tree, which takes the value of the sequence as the subscript. The value counted in the node is the occurrence times of all numbers in the value field of \ ([l,r] \) in the interval \ ([l,r] \) corresponding to the node.
For example, there is a sequence \ (\ {1,5,2,3,4,1,3,4,4 \} \) with a length of \ (10 \).
Then count the number of occurrences of each number. It is easy to know that \ (1 \) has occurred \ (2 \) times, \ (2 \) times, \ (3 \) times, \ (2 \), \ (4 \), and \ (5 \) times.
Then we can build such a weight segment tree:
From the Internet. The number in the node represents the occurrence times of all numbers in the corresponding interval of the node.
let me put it another way,
Value of each leaf node: represents the number of occurrences of this value.
Value of non leaf node: represents the sum of the occurrence times of all values in a value field.
In the weight segment tree above, \ (6,7,8 \) does not appear, but it is built. If the value range of the number of sequences \ (a_i \) is \ (w \), our tree needs \ (O(w\log w) \) space. This is unbearable for most questions.
So consider the dynamic opening point. For a general segment tree, for a node \ (p \), its \ (ls,rs \) is generally \ (p\times 2,p\times 2+1 \), and here we directly define two arrays ls[p],rs[p] to represent the left and right sons of the node \ (p \).
In this way, we will create \ (O(n) \) leaf nodes, and for each leaf node, there is \ (O(\log w) \) depth on the network, so the total spatial complexity is reduced to \ (O(n\log w) \).
Consider how to implement the tree building process in code.
inline void pushup(int p){ tr[p]=tr[ls[p]]+tr[rs[p]]; return; } inline void update(int &p,int l,int r,int now){ if(!p)p=++id; if(l==r){ tr[p]++; return; } int mid=(l+r)>>1; if(now<=mid)update(ls[p],l,mid,now); else update(rs[p],mid+1,r,now); pushup(p); return; }
We can use build in the process of update.
We pass \ (p \) as a parameter. If \ (p \) does not appear, we can directly create a new node with + + id.
l. R represents the interval represented by the current node. Now represents the value added by the current update to the weight segment tree, so we will add one to the values of all intervals containing now.
The rest is implemented in the example.
II Example of weight line segment tree
I believe everyone has done it.
Then consider using the weighted segment tree method.
Find the reverse order pair, traverse the array from left to right, and when traversing to \ (i \), check how many of the traversed values \ (a_1\sim a_{i-1} \) are larger than it.
This can be achieved by using the weight segment tree.
#include<bits/stdc++.h> using namespace std; const int N=1e7+8; const int lim=1e9; int tr[N],ls[N],rs[N],n,id,rt; long long ans; inline void pushup(int p){ tr[p]=tr[ls[p]]+tr[rs[p]]; return; } inline void update(int &p,int l,int r,int col){ if(!p)p=++id; if(l==r){ tr[p]++; return; } int mid=(l+r)>>1; if(col<=mid)update(ls[p],l,mid,col); else update(rs[p],mid+1,r,col); pushup(p); return; } inline long long query(int p,int l,int r,int now){//now here represents the left end point of the query interval. The right endpoint is lim, so no parameters are passed. if(!p)return 0; if(l>now)return (long long)tr[p];//That is, how many numbers are greater than x int mid=(l+r)>>1; long long res=0; if(now<=mid)res+=query(ls[p],l,mid,now); res+=query(rs[p],mid+1,r,now);//Since t is lim, it must be greater than mid without judgment. return res; } int main(){ scanf("%d",&n); for(int i=1;i<=n;i++){ int x; scanf("%d",&x); update(rt,1,lim,x);//rt is the root of the current weight segment tree. ans+=query(1,1,lim,x);//query(1,1,lim,x,lim) should be written here. I omitted the last lim. //These two lines are equivalent to adding x to the weight segment tree to query the number of values greater than x in the weight segment tree. } printf("%lld",ans); return 0; }
Consider maintaining an additional information flag[p] in the segment tree node to represent whether there are elements that appear only once in this value range.
Then, when asking, give priority to the right (the part with larger value range).
Note that the value range \ (a_i \) may be negative, so this is a trick: add delta to \ (a_i \) to make \ (a_i \) positive.
delta is \ (10 ^ 9 \) for this problem. Note that lim becomes \ (2\times 10^9 \).
#include<bits/stdc++.h> using namespace std; #define ll long long const int N=1e7+8; const int lim=2e9; const int delta=1e9; int tr[N],ls[N],rs[N],flag[N],k,n,id,rt,a[N]; inline void pushup(int p){ tr[p]=tr[ls[p]]+tr[rs[p]]; flag[p]=flag[ls[p]]|flag[rs[p]]; return; } inline void update(int &p,int l,int r,int col,int val){ // cout<<p<<" "<<l<<" "<<r<<"*"; if(!p)p=++id; if(l==r){ tr[p]+=val; if(tr[p]==1)flag[p]=1; else flag[p]=0; return; } int mid=(r-l)>>1; mid+=l; if(col<=mid)update(ls[p],l,mid,col,val); else update(rs[p],mid+1,r,col,val); pushup(p); return; } inline int query(int p,int l,int r){ if(!flag[p])return -1; if(l==r)return l; int mid=(r-l)>>1; mid+=l; // int mid=(ll)((l+r)>>1); if(flag[rs[p]])return query(rs[p],mid+1,r); else return query(ls[p],l,mid); } int main(){ scanf("%d%d",&n,&k); for(int i=1;i<=n;i++)scanf("%d",&a[i]),a[i]+=delta; for(int i=1;i<k;i++)update(rt,0,lim,a[i],1); for(int i=1;i<=n-k+1;i++){ int j=i+k-1; update(rt,0,lim,a[j],1); int x=query(rt,0,lim); if(x==-1)puts("Nothing"); else printf("%d\n",x-delta); update(rt,0,lim,a[i],-1); } return 0; }
The weight segment tree can realize all the functions of the template balance tree.
- Insert and delete
update directly.
inline void pushup(int p){ tr[p]=tr[ls[p]]+tr[rs[p]]; return; } inline void update(int &p,int l,int r,int now,int val){ if(!p)p=++id; if(l==r){ tr[p]+=val; return; } int mid=(l+r)>>1; if(now<=mid)update(ls[p],l,mid,now,val); else update(rs[p],mid+1,r,now,val); pushup(p); return; } inline void add(int x){ update(rt,1,lim,x,1); return; } inline void del(int x){ update(rt,1,lim,x,-1); return; }
- Query the ranking of \ (x \)
It is also very simple. Directly query the number of numbers in \ ([1,x-1] \), then the answer is to add one.
inline int query(int p,int l,int r,int s,int t){ if(s<=l&&r<=t)return tr[p]; int mid=(l+r)>>1,res=0; if(s<=mid)res+=query(ls[p],l,mid,s,t); if(mid<t)res+=query(rs[p],mid+1,r,s,t); return res; } inline int rnk(int x){ if(x<=1)return 1; return query(1,1,lim,1,x-1)+1; }
- Query the \ (k \) largest
When the weight in the left subtree is greater than or equal to the number of remaining queries, it recursively enters the left subtree for query, otherwise it recursively enters the right subtree for query and subtracts the weight of the left subtree.
inline int kth(int p,int l,int r,int now){ if(l==r)return l; int mid=(l+r)>>1; if(now<=tr[ls[p]])return kth(ls[p],l,mid,now); else return kth(rs[p],mid+1,r,now-tr[ls[p]]); }
- Predecessor and successor
Take the previous drive as an example.
Consider the classification of the relationship between \ ([l,r] \) and \ (x \).
If \ (r < x \), directly query the rightmost point in the \ ([l,r] \) interval.
If \ (x > mid + 1 \) and \ (p \) has a right son, first query whether there are \ (< x \) nodes in \ ([mid+1,r] \).
If none of the above is met, query \ ([l,mid] \).
The same goes for the successor.
inline int findpre(int p,int l,int r){ if(l==r)return l; int mid=(l+r)>>1; if(tr[rs[p]])return findpre(rs[p],mid+1,r); else return findpre(ls[p],l,mid); } inline int pre(int p,int l,int r,int now){ int mid=(l+r)>>1; if(r<now){ if(tr[p])return findpre(p,l,r); else return 0; } else if(now>mid+1&&tr[rs[p]]){ int res=pre(rs[p],mid+1,r,now); if(res)return res; } return pre(ls[p],l,mid,now); } inline int findnxt(int p,int l,int r){ if(l==r)return l; int mid=(l+r)>>1; if(tr[ls[p]])return findnxt(ls[p],l,mid); else return findnxt(rs[p],mid+1,r); } inline int nxt(int p,int l,int r,int now){ int mid=(l+r)>>1; if(l>now){ if(tr[p])return findnxt(p,l,r); else return 0; } else if(now<=mid&&tr[ls[p]]){ int res=nxt(ls[p],l,mid,now); if(res)return res; } return nxt(rs[p],mid+1,r,now); }
Total code:
#include<bits/stdc++.h> using namespace std; const int N=1e5+6; const int lim=2e7; const int M=1e7+8; const int delta=1e7; int tr[M],ls[M],rs[M],id,rt,n; inline void pushup(int p){ tr[p]=tr[ls[p]]+tr[rs[p]]; return; } inline void update(int &p,int l,int r,int now,int val){ if(!p)p=++id; if(l==r){ tr[p]+=val; return; } int mid=(l+r)>>1; if(now<=mid)update(ls[p],l,mid,now,val); else update(rs[p],mid+1,r,now,val); pushup(p); return; } inline void add(int x){ update(rt,1,lim,x,1); return; } inline void del(int x){ update(rt,1,lim,x,-1); return; } inline int query(int p,int l,int r,int s,int t){ if(s<=l&&r<=t)return tr[p]; int mid=(l+r)>>1,res=0; if(s<=mid)res+=query(ls[p],l,mid,s,t); if(mid<t)res+=query(rs[p],mid+1,r,s,t); return res; } inline int rnk(int x){ if(x<=1)return 1; return query(1,1,lim,1,x-1)+1; } inline int kth(int p,int l,int r,int now){ if(l==r)return l; int mid=(l+r)>>1; if(now<=tr[ls[p]])return kth(ls[p],l,mid,now); else return kth(rs[p],mid+1,r,now-tr[ls[p]]); } inline int findpre(int p,int l,int r){ if(l==r)return l; int mid=(l+r)>>1; if(tr[rs[p]])return findpre(rs[p],mid+1,r); else return findpre(ls[p],l,mid); } inline int pre(int p,int l,int r,int now){ int mid=(l+r)>>1; if(r<now){ if(tr[p])return findpre(p,l,r); else return 0; } else if(now>mid+1&&tr[rs[p]]){ int res=pre(rs[p],mid+1,r,now); if(res)return res; } return pre(ls[p],l,mid,now); } inline int findnxt(int p,int l,int r){ if(l==r)return l; int mid=(l+r)>>1; if(tr[ls[p]])return findnxt(ls[p],l,mid); else return findnxt(rs[p],mid+1,r); } inline int nxt(int p,int l,int r,int now){ int mid=(l+r)>>1; if(l>now){ if(tr[p])return findnxt(p,l,r); else return 0; } else if(now<=mid&&tr[ls[p]]){ int res=nxt(ls[p],l,mid,now); if(res)return res; } return nxt(rs[p],mid+1,r,now); } int main(){ scanf("%d",&n); for(int i=1;i<=n;i++){ int op,x; scanf("%d%d",&op,&x); x+=delta; if(op==1)add(x); else if(op==2)del(x); else if(op==3)printf("%d\n",rnk(x)); else if(op==4)printf("%d\n",kth(rt,1,lim,x-delta)-delta); else if(op==5)printf("%d\n",pre(rt,1,lim,x)-delta); else printf("%d\n",nxt(rt,1,lim,x)-delta); } return 0; }
Exercise:
\(\ text{P1637} \) ternary ascending subsequence
\(\ text{P6186} \) [\ (\ text{NOI Online} \) improvement group] bubble sorting
III Segment tree merging
The interval represented by the points to be merged in the two segment trees is the same.
inline int merge(int x,int y,int l,int r){ if(!x||!y)return x+y; if(l==r){ tr[x]=tr[x]+tr[y]; return x; } int mid=(l+r)>>1; ls[x]=merge(ls[x],ls[y],l,mid); rs[x]=merge(rs[x],rs[y],mid+1,r); pushup(x); return x; }