# TREAP ## 特性 treap=tree+heap ### tree 指binary search tree 可以直接在上面二分搜 ![](https://upload.wikimedia.org/wikipedia/commons/d/da/Binary_search_tree.svg) ### heap 每個節點都比他的子節點大(或小) ![](https://upload.wikimedia.org/wikipedia/commons/c/c4/Max-Heap-new.svg) ### treap ![](https://upload.wikimedia.org/wikipedia/commons/e/e4/Treap.svg) 每個節點會有: pri值(縱軸): heap特性,為了保持平衡而隨機決定 key值(橫軸): tree特性,其實就是原序列中的位置 以及其他你想維護的資訊 ```cpp= struct treap{ int pri,key,val; treap *lc,*rc; treap(int _key,int _val):key(_key),val(_val){ pri=rand(); lc=rc=nullptr; } }; ``` treap 的操作都是把要做事的區間split 出來,詢問或操作完成後再merge回去 ## split ![](https://upload.wikimedia.org/wikipedia/commons/6/69/Treap_split.svg) ```cpp= void split(treap* t,int k,treap*& l,treap*& r){ //把t中key值小於等於k的分到l,其他分到r if(!t) l=r=nullptr; else if(t->key<=k){ l=t; split(t->rc, k, l->rc, r); } else{ r=t; split(t->lc, k, l, r->lc); } } ``` ## merge ![](https://upload.wikimedia.org/wikipedia/commons/a/a8/Treap_merge.svg) ```cpp= treap* merge(treap* a,treap* b){ if(!a||!b) return a? a:b; if(a->pri>b->pri){ a->rc=merge(a->rc,b); return a; } else{ b->lc=merge(a,b->lc); return b; } } ``` 只要在用的時候確保a->key小於b->key,就只要檢查heap的特性就好(即pri值的大小) ## 應用 反正就記住把要做事的區間split 出來,詢問或操作完成後再merge回去 :::warning :warning:常數通常比線段樹大,斟酌使用 ::: ### 插入 ```cpp= treap insert(treap *t,int idx,int k){ treap *a,*b; split(t,idx,a,b); return merge(a,merge(new treap(idx,k),b)); } ``` ### 刪除 ```cpp= treap remove(treap *t,int idx){ treap *a,*b; split(t,idx-1,a,t); split(t,0,t,b); return merge(a,b); } ``` ### RMQ [Dynamic Range Minimum Queries](https://cses.fi/problemset/task/1649) 加個pull就好了 sum, XOR, gcd也都差不多 :::spoiler code: ```cpp= #include<bits/stdc++.h> using namespace std; struct treap{ int pri,key,val,mn; treap *lc,*rc; treap(int _key,int _val):key(_key),val(_val){ mn=val; pri=rand(); lc=rc=nullptr; } }; void pull(treap *&a){ a->mn=a->val; if(a->lc) a->mn=min(a->mn,a->lc->mn); if(a->rc) a->mn=min(a->mn,a->rc->mn); } void split(treap *t,int k,treap *&l,treap *&r){ if(!t) l=r=nullptr; else if(t->key<=k){ l=t; split(t->rc, k, l->rc, r); pull(l);//注意這邊 } else{ r=t; split(t->lc, k, l, r->lc); pull(r);//注意這邊 } } treap* merge(treap *a,treap *b){ if(!a||!b) return a? a:b; if(a->pri>b->pri){ a->rc=merge(a->rc,b); pull(a);//注意這邊 return a; } else{ b->lc=merge(a,b->lc); pull(b);//注意這邊 return b; } } void upd(treap *&t,int idx,int k){ treap *a,*b; split(t,idx-1,a,t); split(t,idx,t,b); t->val=t->mn=k; t=merge(a,merge(t,b)); } int query(treap *&t,int l,int r){ treap *a,*b; split(t,l-1,a,t); split(t,r,t,b); int ret=t->mn; t=merge(a,merge(t,b)); return ret; } int main(){ int n,q; cin>>n>>q; treap *tr=NULL; for(int i=0;i<n;i++){ int a; cin>>a; tr=merge(tr,new treap(i,a)); } while(q--){ int a,b,c; cin>>a>>b>>c; if(a==1){ b--; upd(tr,b,c); } else{ b--; c--; cout<<query(tr,b,c)<<endl; } } return 0; } ``` ::: ### 關於key 如果要做的操作會改變節點在原序列的位置怎麼辦,這樣key不就亂掉了嗎 每次移動就更改key? 如果要搬一大段就會需要每一個都改,太麻煩了,需要一個更好維護並且可以推出key的資訊 沒錯就是size key就是整個**treap中有多少人比他小** 以根節點為例,他的key就是左子節點的size 如果在左邊,就直接遞迴往下 如果在右邊,key就是根節點的左子節點的size + 1 + 遞迴往下做的答案 :::spoiler 利用size: ```cpp= struct treap{ int pri,sz,val; treap *lc,*rc; treap(int _val):val(_val){ pri=rand(); sz=1; lc=rc=nullptr; } }; void pull(treap *t){ t->sz=1; if(t->lc) t->sz+=t->lc->sz; if(t->rc) t->sz+=t->rc->sz; } void split(treap *t,int idx,treap *&l,treap *&r){ int lsize=t->lc? t->lc->sz:0; if(!t) l=r=NULL; else if(lsize<=idx){ l=t; split(t->rc,idx-lsize-1,l->rc,r); pull(l); } else{ r=t; split(t->lc,idx,l,r->lc); pull(r); } } treap* merge(treap* a,treap* b){ if(!a||!b) return a? a:b; if(a->pri>b->pri){ a->rc=merge(a->rc,b); pull(a); return a; } else{ b->lc=merge(a,b->lc); pull(b); return b; } } ``` ::: ### 懶標 和線段樹一樣,紀錄**該節點已經做完,子節點還沒**的操作 在此以區間加值,區間求和為例 ```cpp= void push(treap *t){ if(t->lz==0) return; if(t->lc){ t->lc->val+=t->lz; t->lc->sum+=t->lz*t->lc->sz; t->lc->lz+=t->lz; } if(t->rc){ t->rc->val+=t->lz; t->rc->sum+=t->lz*t->rc->sz; t->rc->lz+=t->lz; } } void split(treap *t,int k,treap *&l,treap *&r){ if(!t) l=r=nullptr; else if(t->key<=k){ l=t; push(l);//注意這邊 split(t->rc, k, l->rc, r); pull(l); } else{ r=t; push(r);//注意這邊 split(t->lc, k, l, r->lc); pull(r); } } treap* merge(treap *a,treap *b){ if(!a||!b) return a? a:b; if(a->pri>b->pri){ push(a);//注意這邊 a->rc=merge(a->rc,b); pull(a); return a; } else{ push(b);//注意這邊 b->lc=merge(a,b->lc); pull(b); return b; } } ``` ### 區間反轉 區間反轉就是把要轉的區間split出來,然後把左右子樹對調,子樹也遞迴做一樣的事 然後再加上懶標就好了 ## 題目 [Cut and Paste](https://cses.fi/problemset/task/2072) :::spoiler code(先自己寫看看) ```cpp= #include<bits/stdc++.h> using namespace std; struct treap{ int pri,sz,val; treap *lc,*rc; treap(int _val):val(_val){ pri=rand(); sz=1; lc=rc=nullptr; } }; void pull(treap *t){ t->sz=1; if(t->lc) t->sz+=t->lc->sz; if(t->rc) t->sz+=t->rc->sz; } void split(treap *t,int idx,treap *&l,treap *&r){ if(!t) l=r=NULL; else{ int lsize=t->lc? t->lc->sz:0; if(lsize<=idx){ l=t; split(t->rc,idx-lsize-1,l->rc,r); pull(l); } else{ r=t; split(t->lc,idx,l,r->lc); pull(r); } } } treap* merge(treap* a,treap* b){ if(!a||!b) return a? a:b; if(a->pri>b->pri){ a->rc=merge(a->rc,b); pull(a); return a; } else{ b->lc=merge(a,b->lc); pull(b); return b; } } int main(){ int n,q; cin>>n>>q; treap *tr=NULL; string s; cin>>s; for(int i=0;i<n;i++){ tr=merge(tr,new treap(s[i]-'A')); } while(q--){ int a,b; cin>>a>>b; a--; b--; treap *left,*right; split(tr,a-1,left,tr); split(tr,b-a,tr,right); tr=merge(left,merge(right,tr)); } for(int i=0;i<n;i++){ treap *ans; split(tr,0,ans,tr); char c='A'+ans->val; cout<<c; } return 0; } ``` ::: --- [Substring Reversals](https://cses.fi/problemset/task/2073) :::spoiler code(先自己寫看看) ```cpp= #include<bits/stdc++.h> using namespace std; struct treap{ int pri,sz,val; bool lz; treap *lc,*rc; treap(int _val):val(_val){ lz=0; pri=rand(); sz=1; lc=rc=nullptr; } }; void push(treap *t){ if(!t->lz) return; if(t->lc){ swap(t->lc->lc,t->lc->rc); t->lc->lz^=1; } if(t->rc){ swap(t->rc->lc,t->rc->rc); t->rc->lz^=1; } t->lz=0; } void pull(treap *t){ t->sz=1; if(t->lc) t->sz+=t->lc->sz; if(t->rc) t->sz+=t->rc->sz; } void split(treap *t,int idx,treap *&l,treap *&r){ if(!t) l=r=NULL; else{ int lsize=t->lc? t->lc->sz:0; if(lsize<=idx){ l=t; push(l); split(t->rc,idx-lsize-1,l->rc,r); pull(l); } else{ r=t; push(r); split(t->lc,idx,l,r->lc); pull(r); } } } treap* merge(treap* a,treap* b){ if(!a||!b) return a? a:b; if(a->pri>b->pri){ push(a); a->rc=merge(a->rc,b); pull(a); return a; } else{ push(b); b->lc=merge(a,b->lc); pull(b); return b; } } int main(){ int n,q; cin>>n>>q; treap *tr=NULL; string s; cin>>s; for(int i=0;i<n;i++){ tr=merge(tr,new treap(s[i]-'A')); } while(q--){ int l,r; cin>>l>>r; l--; r--; treap *left,*right; split(tr,l-1,left,tr); split(tr,r-l,tr,right); swap(tr->lc,tr->rc); tr->lz^=1; tr=merge(left,merge(tr,right)); } for(int i=0;i<n;i++){ treap *ans; split(tr,0,ans,tr); char c='A'+ans->val; cout<<c; } return 0; } ``` ::: --- [Reversals and Sums](https://cses.fi/problemset/task/2074) :::spoiler code(先自己寫看看) 我還沒寫 2023.05.16 更 寫完了,我也不知道為什麼拖這麼久 ```cpp= #include<bits/stdc++.h> #define int long long using namespace std; struct treap{ int pri,sz,val,sum; bool lz; treap *lc,*rc; treap(int _val):val(_val){ lz=0; sum=val; pri=rand(); sz=1; lc=rc=nullptr; } }; void push(treap *t){ if(!t->lz) return; if(t->lc){ swap(t->lc->lc,t->lc->rc); t->lc->lz^=1; } if(t->rc){ swap(t->rc->lc,t->rc->rc); t->rc->lz^=1; } t->lz=0; } void pull(treap *t){ t->sum=t->val; t->sz=1; if(t->lc){ t->sz+=t->lc->sz; t->sum+=t->lc->sum; } if(t->rc){ t->sz+=t->rc->sz; t->sum+=t->rc->sum; } } void split(treap *t,int idx,treap *&l,treap *&r){ if(!t) l=r=NULL; else{ int lsize=t->lc? t->lc->sz:0; if(lsize<=idx){ l=t; push(l); split(t->rc,idx-lsize-1,l->rc,r); pull(l); } else{ r=t; push(r); split(t->lc,idx,l,r->lc); pull(r); } } } treap* merge(treap* a,treap* b){ if(!a||!b) return a? a:b; if(a->pri>b->pri){ push(a); a->rc=merge(a->rc,b); pull(a); return a; } else{ push(b); b->lc=merge(a,b->lc); pull(b); return b; } } signed main(){ int n,q; cin>>n>>q; treap* tr=NULL; for(int i=0;i<n;i++){ int a; cin>>a; tr=merge(tr,new treap(a)); } while(q--){ int a; cin>>a; if(a==1){ int l,r; cin>>l>>r; l--; r--; treap *left,*right; split(tr,l-1,left,tr); split(tr,r-l,tr,right); swap(tr->lc,tr->rc); tr->lz^=1; tr=merge(left,merge(tr,right)); } else{ int l,r; cin>>l>>r; l--; r--; treap *left,*right; split(tr,l-1,left,tr); split(tr,r-l,tr,right); cout<<tr->sum<<endl; tr=merge(left,merge(tr,right)); } } return 0; } ``` ::: --- [SuperMemo](https://vjudge.net/problem/POJ-3580) 序列操作大雜燴