1. 程式人生 > >[BZOJ4552][TJOI2016&HEOI2016]排序-線段樹合併

[BZOJ4552][TJOI2016&HEOI2016]排序-線段樹合併

排序

Description

在2016年,佳媛姐姐喜歡上了數字序列。因而他經常研究關於序列的一些奇奇怪怪的問題,現在他在研究一個難題,需要你來幫助他。這個難題是這樣子的:給出一個1到n的全排列,現在對這個全排列序列進行m次區域性排序,排序分為兩種:1:(0,l,r)表示將區間[l,r]的數字升序排序2:(1,l,r)表示將區間[l,r]的數字降序排序最後詢問第q位置上的數字。

Input

輸入資料的第一行為兩個整數n和m。n表示序列的長度,m表示區域性排序的次數。1 <= n, m <= 10^5第二行為n個整數,表示1到n的一個全排列。接下來輸入m行,每一行有三個整數op, l, r, op為0代表升序排序,op為1代表降序排序, l, r 表示排序的區間。最後輸入一個整數q,q表示排序完之後詢問的位置, 1 <= q <= n。1 <= n <= 10^5,1 <= m <= 10^5

Output

輸出資料僅有一行,一個整數,表示按照順序將全部的部分排序結束後第q位置上的數字。

Sample Input

6 3
1 6 2 5 3 4
0 1 4
1 3 6
0 2 4
3

Sample Output

5

本來想學習一下線段樹合併來著……
然後就找到了這題……
然後……怎麼這麼難啊啊啊啊啊啊!!!!!!
調了一晚上+一上午+半下午總算能過了……

思路:
初始對每一個單獨的位置建一棵獨立的線段樹,都像主席樹一樣維護一個值域,每棵樹初始僅有自身位置上的值這一個值加入值域。
為保證接下來能合併,咱必須保證樹的結構相同,又由題面知這是一個全排列,所以每棵線段樹的根節點所表示的值域範圍均為[1,n]。
然後,每次排序就是合併能表示對應排序區間的數棵權值線段樹~
但是,可能有時想要的左端點或右端點被包含在了某棵線段樹所表示的區間內,則此時需要進行拆分對應的線段樹,即split操作。
用一棵獨立的非動態開點線段樹來儲存和查詢在當前需查詢區間的左端點的左邊,最靠近它的一個根節點的左端點,每次在查詢到它後從那個節點開始合併,直到湊出當前排序區間的一棵線段樹,就可以結束操作了~
最後答案直接查詢便可~

特殊方法思路:
merge(合併):遞迴兩棵樹的相同位置的兩個節點,如果有一個為空則返回另一個,否則遞迴呼叫合併,返回值給其中一個節點,最後銷燬另一個節點。
split(拆分):遞迴目標樹和新樹的相同位置的兩個節點,按目標樹當前節點的左兒子size大小判斷遞迴拆分到左還是右,最後重新賦size的值。

注意:
1.對於升序和降序,只需要打個標記,在拆分時特判一下就好~
2.對於空間,可以採用程式碼中的方法用佇列維護空白的節點……
3.細節巨多,因此程式碼中有英文註釋~

那麼,這個難題總算完成了!

#include<iostream>
#include<queue>
#include<cstdio> #include<cstring> #include<cstdlib> #include<algorithm> using namespace std; const int M=2000010; const int N=100010; int n,m; struct roots { int l,r,type,nxt; }root[M]; struct segment_tree { struct data { bool exist; int rmax; }a[N<<2]; inline void update(int x) { a[x]=(data){a[x<<1].exist||a[x<<1|1].exist,a[x<<1|1].rmax?a[x<<1|1].rmax:a[x<<1].rmax}; } void ins(int n,int l,int r,int pos,int val) { if(l==r) { a[n]=(data){val?1:0,val}; return; } int mid=l+r>>1; if(pos<=mid) ins(n<<1,l,mid,pos,val); else ins(n<<1|1,mid+1,r,pos,val); update(n); } int query(int n,int l,int r,int pos) { if(pos<l || (!a[n].exist)) return 0; if(l==r) return a[n].rmax; int mid=l+r>>1,tmp; if(tmp=query(n<<1|1,mid+1,r,pos)) return tmp; else if(mid<=pos) return a[n<<1].rmax; else return query(n<<1,l,mid,pos); } }koishi; struct node { int l,r,sum; }tree[M]; queue<int> available; inline int apply() { int ret=available.front(); available.pop(); return ret; } inline void free(int n) { tree[n]=tree[0]; available.push(n); } void build(int n,int l,int r,int val) { tree[n].sum=1; if(l==r) return; int mid=l+r>>1; if(val<=mid) build(tree[n].l=apply(),l,mid,val); else build(tree[n].r=apply(),mid+1,r,val); } int merge(int n1,int n2) { if(n1==0 || n2==0) return n1+n2; tree[n1].l=merge(tree[n1].l,tree[n2].l); tree[n1].r=merge(tree[n1].r,tree[n2].r); tree[n1].sum+=tree[n2].sum; free(n2); return n1; } void split(int n1,int n2,int k) { int siz=tree[tree[n1].l].sum; if(k>siz) split(tree[n1].r,tree[n2].r=apply(),k-siz); else swap(tree[n2].r,tree[n1].r); if(k<siz) split(tree[n1].l,tree[n2].l=apply(),k); tree[n2].sum=tree[n1].sum-k; tree[n1].sum=k; } int query(int n,int l,int r,int k) { if(l==r) return l; int siz=tree[tree[n].l].sum,mid=l+r>>1; if(k<=siz) return query(tree[n].l,l,mid,k); else return query(tree[n].r,mid+1,r,k-siz); } int main() { for(int i=1;i<=M-10;i++) available.push(i);//save id scanf("%d%d",&n,&m); for(int i=1,last=M-5,val;i<=n;i++) { scanf("%d",&val); root[last].nxt=apply();//new root root[root[last].nxt]=(roots){i,i,0,0};//set current root's val:l=i,r=i koishi.ins(1,1,n,i,root[last].nxt);//add info of now build(root[last].nxt,1,n,val);//build tree for current root last=root[last].nxt;//reset last to current root } int now; while(m--) { int op,l,r; scanf("%d%d%d",&op,&l,&r); int ll=koishi.query(1,1,n,l),rr;//get the nearest root in l's left if(l==root[ll].l)//l already has a root,no need to split { rr=apply();//new right swap(tree[rr],tree[ll]);//move ll's info into rr swap(root[rr],root[ll]);//move ll's info into rr root[now=ll]=(roots){l,r,op,rr};//use ll buid a new root in this range } else { root[now=apply()]=(roots){l,r,op,rr=apply()};//apply now and nxt change to rr if(!root[ll].type)//if up split(ll,rr,l-root[ll].l);//cut [ll.l,l] from ll to rr else//down split(ll,rr,root[ll].r-l+1),//cut [ll.l,ll.l+ll.r-l+1] from ll to rr swap(tree[ll],tree[rr]);//swap ll's info to rr([ll.l+ll.r-l+2,ll.r] is the ans we need) root[rr]=root[ll];//copy root info root[rr].l=l;//reset l root[ll].r=l-1;//rest ll's r root[ll].nxt=now;//reset nxt=now } int nxt,last,tmp; for(nxt=rr;nxt && r>=root[nxt].r;) { merge(now,nxt);//from now to r merge all koishi.ins(1,1,n,root[nxt].l,0);//clear info of nxt last=nxt;//set nxt as last nxt=root[nxt].nxt;//nxt rightmove root[last]=root[0];//clear nxt val; } root[now].nxt=nxt;//get the new range([l,r])'s nxt if(nxt!=0 && root[nxt].l<=r)//if nxt's l is in [l,r] range { koishi.ins(1,1,n,root[nxt].l,0);//clear info of nxt if(root[nxt].type) split(nxt,tmp=apply(),root[nxt].r-r);//cut [nxt.l,r] to a new tmp else split(nxt,tmp=apply(),r-root[nxt].l+1),//cut [nxt.l,r+1] to tmp swap(tree[nxt],tree[tmp]); merge(now,tmp);//merge now with tmp root[nxt].l=r+1;//update nxt.l koishi.ins(1,1,n,root[nxt].l,nxt);//reset info for nxt } koishi.ins(1,1,n,l,now);//add info for now } int pos; scanf("%d",&pos); now=koishi.query(1,1,n,pos);//query in which root if(root[now].type) printf("%d\n",query(now,1,n,root[now].r-pos+1));//query pos else printf("%d\n",query(now,1,n,pos-root[now].l+1));//query pos return 0; }