1. 程式人生 > >【算法】fhqtreap初探

【算法】fhqtreap初探

oid 最好 回收 truct iostream 實現 min define hide

  NOIP回來就一直想著學平衡樹。。。平衡樹寫久了調不出來真的會頭腦發熱.jpg

  大概只寫了幾道題。。。

  fhqtreap是不需要旋(xun)轉(jun)的平衡樹,僅使用分裂合並,一樣可以保持平衡樹的性質,並且可以非常簡單地處理區間問題。

  fhqtreap的核心有兩端代碼,split(分裂)和merge(合並)

  split(x, l, r, k),表示把原x的子樹以第k大數為界限,權值<=第k大數的數分在左子樹,根為l,其他的分在右子樹,根為r

技術分享圖片
void split(int x, int &l, int &r, int k)
{
    
if(!k) l=0, r=x; else if(k==tree[x].size) l=x, r=0; else if(k<=tree[lt].size) down(r=x), split(lt, l, lt, k), up(x); else down(l=x), split(rt, rt, r, k-tree[lt].size-1), up(x); }
View Code

  merge(x, l, r),表示把根為l的子樹和根為r的子樹合並成一棵根為x的子樹

技術分享圖片
void merge(int &x, int l, int r)
{
    
if(!l || !r) x=l+r; else if(tree[l].rnd<tree[r].rnd) down(x=l), merge(tree[x].rs, tree[x].rs, r), up(x); else down(x=r), merge(tree[x].ls, l, tree[x].ls), up(x); }
View Code

  fhqtreap能實現哪些操作呢?

  單點/區間插入,單點/區間刪除,區間加,區間查詢和,區間查詢最值,區間反轉,區間旋(xun)轉(jun)(其實就是這個區間整體後移k步,超過區間的補到前面),還有等等...splay和線段樹能做的大部分都能夠做到...並且常數比splay小的多...

  查詢一個區間[l, r]就把一棵樹split成三棵樹,查中間那棵,再把它們merge回去

核心代碼:

技術分享圖片
inline void add(int l, int r, int delta)//任意操作
{
    int x, y, z;
    split(root, x, y, r); split(x, z, x, l-1);//拆成z,x,y三棵
    addone(x, delta);//任意操作
    merge(x, z, x); merge(root, x, y);//並回去
}
View Code

  隨機數可以用rand()<<15|rand()來求

  初始最好tree[0].rnd=tree[0].sum=tree[0].xxx=...=inf,否則up的時候可能求min會GG,同理求max要賦值-inf

例題時間~

  例1 bzoj 3223 tyvj 1729

  經典題。。。需要多運用一個rank查詢x數的排名,才能進行split(否則得另寫一個按數字分的split,相比起來這樣更好寫)

技術分享圖片
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<algorithm>
#define ll long long
#define lt tree[x].ls
#define rt tree[x].rs
using namespace std;
const int maxn=500010, inf=1e9+1;
int n, m, x, y, z, root, tott, tmp;
struct treap{int rnd, mn, sum, delta, rev, size, ls, rs;} tree[maxn];
char s[20];
inline void read(int &k)
{
    int f=1; k=0; char c=getchar();
    while(c<0 || c>9) c==-&&(f=-1), c=getchar();
    while(c<=9 && c>=0) k=k*10+c-0, c=getchar();
    k*=f;    
} 
inline int min(int a, int b){return a<b?a:b;}
inline void addone(int x, int delta) {if(!x) return; tree[x].sum+=delta; tree[x].delta+=delta; tree[x].mn+=delta;}
inline void reverseone(int x) {tree[x].rev^=1; swap(lt, rt);}
inline void build(int &x, int delta) {tree[x=++tott].rnd=rand()<<15|rand(); tree[x].sum=tree[x].mn=delta; tree[x].size=1;}
inline void up(int x) 
{
    if(!x) return;
    tree[x].mn=min(tree[x].sum, min(tree[lt].mn, tree[rt].mn)); 
    tree[x].size=tree[lt].size+tree[rt].size+1;
}
inline void down(int x)
{
    if(!x) return;
    if(tree[x].delta) addone(lt, tree[x].delta), addone(rt, tree[x].delta);
    if(tree[x].rev) reverseone(lt), reverseone(rt);
    tree[x].delta=tree[x].rev=0;
}
void merge(int &x, int l, int r)
{
    if(!l || !r) x=l+r;
    else if(tree[l].rnd<tree[r].rnd) down(x=l), merge(tree[x].rs, tree[x].rs, r), up(x);
    else down(x=r), merge(tree[x].ls, l, tree[x].ls), up(x);
}
void split(int x, int &l, int &r, int k)
{
    if(!k) l=0, r=x;
    else if(k==tree[x].size) l=x, r=0;
    else if(k<=tree[lt].size) down(r=x), split(lt, l, lt, k), up(x);
    else down(l=x), split(rt, rt, r, k-tree[lt].size-1), up(x);
}
inline void insert(int pos, int delta)
{
    int x, y;
    split(root, x, y, pos);
    merge(x, x, delta); merge(root, x, y);
}
inline void del(int pos)
{
    int x, y, z;
    split(root, x, y, pos); split(x, x, z, pos-1);
    merge(root, x, y);
}
inline void add(int l, int r, int delta)
{
    int x, y, z;
    split(root, x, y, r); split(x, z, x, l-1);
    addone(x, delta);
    merge(x, z, x); merge(root, x, y);
}
inline void reverse(int l, int r)
{
    int x, y, z;
    split(root, x, y, r); split(x, z, x, l-1);
    reverseone(x);
    merge(x, z, x); merge(root, x, y);
}
inline void revolve(int l, int r, int delta)
{
    int x, y, z, h;
    split(root, x, y, r-delta); split(x, z, x, l-1); split(y, y, h, delta);
    merge(x, y, x); merge(x, z, x); merge(root, x, h);
}
inline int query(int l, int r)
{
    int x, y, z, ans;
    split(root, x, y, r); split(x, z, x, l-1);
    ans=tree[x].mn;
    merge(x, z, x); merge(root, x, y);
    return ans;
}
int main()
{
    srand(19260817); read(n); tree[0].rnd=tree[0].mn=tree[0].sum=inf;
    for(int i=1;i<=n;i++) read(x), build(tmp, x), merge(root, root, tmp);
    read(m);
    for(int i=1;i<=m;i++)
    {
        scanf("%s", s+1);
        if(s[1]==A) read(x), read(y), read(z), add(x, y, z);
        else if(s[1]==R && s[4]==O) read(x), read(y), read(z), revolve(x, y, z%(y-x+1));
        else if(s[1]==R && s[4]==E) read(x), read(y), reverse(x, y);
        else if(s[1]==D) read(x), del(x);
        else if(s[1]==I) read(x), read(y), build(tmp, y), insert(x, tmp);
        else read(x), read(y), printf("%d\n", query(x, y));
    }
}
View Code

  例2 poj 3580

  整合了大部分操作。。區間加,區間反轉,區間旋(xun)轉(jun),單點插入,單點刪除,區間查詢最小值

  區間反轉打標記就好了,down的時候交換左右子樹,給左右子樹打上標記即可,打標記的方式是^=1

技術分享圖片
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<algorithm>
#define ll long long
#define lt tree[x].ls
#define rt tree[x].rs
using namespace std;
const int maxn=500010, inf=1e9+1;
int n, m, x, y, z, root, tott, tmp;
struct treap{int rnd, mn, sum, delta, rev, size, ls, rs;} tree[maxn];
char s[20];
inline void read(int &k)
{
    int f=1; k=0; char c=getchar();
    while(c<0 || c>9) c==-&&(f=-1), c=getchar();
    while(c<=9 && c>=0) k=k*10+c-0, c=getchar();
    k*=f;    
} 
inline int min(int a, int b){return a<b?a:b;}
inline void addone(int x, int delta) {if(!x) return; tree[x].sum+=delta; tree[x].delta+=delta; tree[x].mn+=delta;}
inline void reverseone(int x) {tree[x].rev^=1; swap(lt, rt);}
inline void build(int &x, int delta) {tree[x=++tott].rnd=rand()<<15|rand(); tree[x].sum=tree[x].mn=delta; tree[x].size=1;}
inline void up(int x) 
{
    if(!x) return;
    tree[x].mn=min(tree[x].sum, min(tree[lt].mn, tree[rt].mn)); 
    tree[x].size=tree[lt].size+tree[rt].size+1;
}
inline void down(int x)
{
    if(!x) return;
    if(tree[x].delta) addone(lt, tree[x].delta), addone(rt, tree[x].delta);
    if(tree[x].rev) reverseone(lt), reverseone(rt);
    tree[x].delta=tree[x].rev=0;
}
void merge(int &x, int l, int r)
{
    if(!l || !r) x=l+r;
    else if(tree[l].rnd<tree[r].rnd) down(x=l), merge(tree[x].rs, tree[x].rs, r), up(x);
    else down(x=r), merge(tree[x].ls, l, tree[x].ls), up(x);
}
void split(int x, int &l, int &r, int k)
{
    if(!k) l=0, r=x;
    else if(k==tree[x].size) l=x, r=0;
    else if(k<=tree[lt].size) down(r=x), split(lt, l, lt, k), up(x);
    else down(l=x), split(rt, rt, r, k-tree[lt].size-1), up(x);
}
inline void insert(int pos, int delta)
{
    int x, y;
    split(root, x, y, pos);
    merge(x, x, delta); merge(root, x, y);
}
inline void del(int pos)
{
    int x, y, z;
    split(root, x, y, pos); split(x, x, z, pos-1);
    merge(root, x, y);
}
inline void add(int l, int r, int delta)
{
    int x, y, z;
    split(root, x, y, r); split(x, z, x, l-1);
    addone(x, delta);
    merge(x, z, x); merge(root, x, y);
}
inline void reverse(int l, int r)
{
    int x, y, z;
    split(root, x, y, r); split(x, z, x, l-1);
    reverseone(x);
    merge(x, z, x); merge(root, x, y);
}
inline void revolve(int l, int r, int delta)
{
    int x, y, z, h;
    split(root, x, y, r-delta); split(x, z, x, l-1); split(y, y, h, delta);
    merge(x, y, x); merge(x, z, x); merge(root, x, h);
}
inline int query(int l, int r)
{
    int x, y, z, ans;
    split(root, x, y, r); split(x, z, x, l-1);
    ans=tree[x].mn;
    merge(x, z, x); merge(root, x, y);
    return ans;
}
int main()
{
    srand(19260817); read(n); tree[0].rnd=tree[0].mn=tree[0].sum=inf;
    for(int i=1;i<=n;i++) read(x), build(tmp, x), merge(root, root, tmp);
    read(m);
    for(int i=1;i<=m;i++)
    {
        scanf("%s", s+1);
        if(s[1]==A) read(x), read(y), read(z), add(x, y, z);
        else if(s[1]==R && s[4]==O) read(x), read(y), read(z), revolve(x, y, z%(y-x+1));
        else if(s[1]==R && s[4]==E) read(x), read(y), reverse(x, y);
        else if(s[1]==D) read(x), del(x);
        else if(s[1]==I) read(x), read(y), build(tmp, y), insert(x, tmp);
        else read(x), read(y), printf("%d\n", query(x, y));
    }
}
View Code

  例3 bzoj 1251

  除了最小值變最大值之外,操作是poj 3580的子集。。。直接搬一下就好。。。

技術分享圖片
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<algorithm>
#define lt tree[x].ls
#define rt tree[x].rs
using namespace std;
const int maxn=50010, inf=1e9+1;
int n, m, ty, x, y, z, root, tott;
struct treap{int rnd, mx, sum, delta, rev, size, ls, rs;} tree[maxn];
char buf[20000010],*ptr=buf-1;
inline int read()
{
    char c=*++ptr; int s=0,t=1;
    while(c<48||c>57) t=-1, c=*++ptr;
    while(c>=48&&c<=57) s=s*10+c-0, c=*++ptr;
    return s*t;
}
inline int max(int a, int b){return a>b?a:b;}
inline void addone(int x, int delta) {tree[x].sum+=delta; tree[x].delta+=delta; tree[x].mx+=delta;}
inline void reverseone(int x) {tree[x].rev^=1; swap(lt, rt);}
inline void up(int x) 
{
    if(!x) return;
    tree[x].mx=max(tree[x].sum, max(tree[lt].mx, tree[rt].mx)); 
    tree[x].size=tree[lt].size+tree[rt].size+1;
}
inline void down(int x)
{
    if(!x) return;
    if(tree[x].delta) addone(lt, tree[x].delta), addone(rt, tree[x].delta);
    if(tree[x].rev) reverseone(lt), reverseone(rt);
    tree[x].delta=tree[x].rev=0;
}
void merge(int &x, int l, int r)
{
    if(!l || !r) x=l+r;
    else if(tree[l].rnd<tree[r].rnd) down(x=l), merge(tree[x].rs, tree[x].rs, r), up(x);
    else down(x=r), merge(tree[x].ls, l, tree[x].ls), up(x);
}
void split(int x, int &l, int &r, int k)
{
    if(!k) l=0, r=x;
    else if(k==tree[x].size) l=x, r=0;
    else if(k<=tree[lt].size) down(r=x), split(lt, l, lt, k), up(x);
    else down(l=x), split(rt, rt, r, k-tree[lt].size-1), up(x);
}
inline void add(int l, int r, int delta)
{
    int x, y, z;
    split(root, x, y, r); split(x, z, x, l-1);
    addone(x, delta);
    merge(x, z, x); merge(root, x, y);
}
inline void reverse(int l, int r)
{
    int x, y, z;
    split(root, x, y, r); split(x, z, x, l-1);
    reverseone(x);
    merge(x, z, x); merge(root, x, y);
}
inline int query(int l, int r)
{
    int x, y, z, ans;
    split(root, x, y, r); split(x, z, x, l-1);
    ans=tree[x].mx;
    merge(x, z, x); merge(root, x, y);
    return ans;
}
int main()
{
    fread(buf,1,sizeof(buf),stdin); n=read(); m=read(); tree[0].rnd=inf; tree[0].mx=tree[0].sum=-inf;
    for(int i=1;i<=n;i++) tree[++tott].size=1, tree[tott].rnd=rand()<<15|rand(), merge(root, root, tott); 
    for(int i=1;i<=m;i++)
    {
        ty=read(); x=read(); y=read();
        if(ty==1) z=read(), add(x, y, z);
        else if(ty==2) reverse(x, y);
        else printf("%d\n", query(x, y));
    }
}
View Code

  例4 bzoj 3223

  陶冶身心的水題。。。區間反轉一個操作而已。。233

技術分享圖片
                        #include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<algorithm>
#define ll long long
#define lt tree[x].ls
#define rt tree[x].rs
using namespace std;
const int maxn=500010, inf=1e9+1;
int n, m, x, y, z, root, tott, tmp;
struct treap{int rnd, sum, rev, size, ls, rs;} tree[maxn];
inline void read(int &k)
{
    int f=1; k=0; char c=getchar();
    while(c<0 || c>9) c==-&&(f=-1), c=getchar();
    while(c<=9 && c>=0) k=k*10+c-0, c=getchar();
    k*=f;    
} 
inline void reverseone(int x) {tree[x].rev^=1; swap(lt, rt);}
inline void build(int &x, int delta) {tree[x=++tott].rnd=rand()<<15|rand(); tree[x].sum=delta; tree[x].size=1;}
inline void up(int x) {if(!x) return; tree[x].size=tree[lt].size+tree[rt].size+1;}
inline void down(int x)
{
    if(!x) return;
    if(tree[x].rev) reverseone(lt), reverseone(rt);
    tree[x].rev=0;
}
void merge(int &x, int l, int r)
{
    if(!l || !r) x=l+r;
    else if(tree[l].rnd<tree[r].rnd) down(x=l), merge(tree[x].rs, tree[x].rs, r), up(x);
    else down(x=r), merge(tree[x].ls, l, tree[x].ls), up(x);
}
void split(int x, int &l, int &r, int k)
{
    if(!k) l=0, r=x;
    else if(k==tree[x].size) l=x, r=0;
    else if(k<=tree[lt].size) down(r=x), split(lt, l, lt, k), up(x);
    else down(l=x), split(rt, rt, r, k-tree[lt].size-1), up(x);
}
inline void reverse(int l, int r)
{
    int x, y, z;
    split(root, x, y, r); split(x, z, x, l-1);
    reverseone(x);
    merge(x, z, x); merge(root, x, y);
}
void print(int x)
{
    if(!x) return; down(x);
    print(lt); printf("%d ", tree[x].sum); print(rt);
}
int main()
{
    srand(19260817); read(n); read(m); tree[0].rnd=tree[0].sum=inf;
    for(int i=1;i<=n;i++) build(tmp, i), merge(root, root, tmp);
    for(int i=1;i<=m;i++) read(x), read(y), reverse(x, y);
    print(root);
}
                    
View Code

  例5 bzoj 1500

  ...大boss,頭皮發麻過幾天再補,需要垃圾回收,線性建樹,讓我冷靜一下...

【算法】fhqtreap初探