文藝平衡樹算法


一、文藝平衡樹解決什么問題

您需要寫一種數據結構(可參考題目標題),來維護一個有序數列。

其中需要提供以下操作:翻轉一個區間,例如原有序序列是 5 4 3 2 15\ 4\ 3\ 2\ 15 4 3 2 1,翻轉區間是 [2,4][2,4][2,4] 的話,結果是 5 2 3 4 15\ 2\ 3\ 4\ 15 2 3 4 1。

 

二、文藝平衡樹與平衡樹

a[5]={5,4,3,1,2};那么存入文藝平衡樹后,再中序遍歷的結果應該還是:{5,4,3,1,2}。
    即下標從小到大,而不是里面的值從小到大!這是與SBT樹(平衡樹)最大的不同!
    文藝平衡樹經過rotates旋轉后,它的中序遍歷結果是不變的(即,下標從小到大)
    但是讓這棵樹的一部分區間倒置之后。這個中序遍歷下標就不是遞增的了(所以它不是平衡樹)

 平衡樹算法 

 

三、文藝平衡樹構造

1、建樹

就像線段樹建樹一樣,但是在原數據的基礎上加上一個-INF,+INF。(比如原序列是1,2,3,4.你建樹的時候要給-INF,1,2,3,4,+INF建樹)

至於為什么要這樣做,就是為了可以給區間[1,n]倒置

 

主函數:

 1 int main()
 2 {
 3     scanf("%d%d",&n,&m);
 4     for (int i=1; i<=n; i++) data[i+1]=i;
 5     data[1]=-INF;
 6     data[n+2]=INF;
 7     rt=build_tree(0,1,n+2);
 8     for (int i=1; i<=m; i++)
 9     { //m是要翻轉多少次區間
10         scanf("%d%d",&x,&y);
11         turn(x,y);
12     }
13     write(rt);
14     return 0;
15 }

這個沒有輸入對應的n個數,而是用1,2......n來代表,你也可以輸入

 

 1 bool get(int x)  //得到x是右孩子還是左孩子
 2 {
 3     return ch[f[x]][1]==x;
 4 }
 5 void pushup(int x)  //更新節點的信息
 6 {
 7     sizes[x]=sizes[ch[x][0]]+sizes[ch[x][1]]+1;
 8 //這個是文藝平衡樹
 9     /*
10     a[5]={5,4,3,1,2};那么存入伸展樹后,再中序遍歷的結果應該還是:{5,4,3,1,2}。
11     即下標從小到大,而不是里面的值從小到大!這是與SBT樹(平衡樹)最大的不同!
12     文藝平衡樹經過rotates旋轉后,它的中序遍歷結果是不變的
13     但是讓這棵樹的一部分區間倒置之后。這個中序遍歷結果就不是遞增的了(所以它不是平衡樹)
14     */
15 }
16 void pushdown(int x)  //相當於線段樹操作的懶惰標記
17 {
18     if(x && tag[x])
19     {
20 //這個tag標記就是用來看這個子樹用不用交換(他的交換也就對應着區間的翻轉)
21         tag[ch[x][0]]^=1;
22         tag[ch[x][1]]^=1;
23         swap(ch[x][0],ch[x][1]);
24         tag[x]=0;
25     }
26 }

 

 1 int build_tree(int fx,int l,int r)
 2 {
 3     //用所給數組data中數據建一棵樹(就是普通線段樹建樹)
 4     if(l>r) return 0;
 5     int mid=(l+r)>>1;
 6     int now=++sz;
 7     key[now]=data[mid];
 8     f[now]=fx;
 9     tag[now]=0;
10 
11     ch[now][0]=build_tree(now,l,mid-1);
12     ch[now][1]=build_tree(now,mid+1,r);
13     pushup(now);
14     return now;
15 }

 

 先序遍歷輸出

void write(int now)
{
    //按照中序遍歷輸出序列
    pushdown(now);
    if(ch[now][0]) write(ch[now][0]);
    if(key[now]!=-INF && key[now]!=INF) printf("%d ",key[now]);
    if(key[ch[now][1]]) write(ch[now][1]);
}

 

 

 

綠色的線就是輸出的過程,綠色的數字就是輸出順序

 

2、區間翻轉操作

參考鏈接:https://blog.csdn.net/a_comme_amour/article/details/79382104

 

旋轉操作和平衡樹旋轉一樣(這里就不重復講了,可以看一下平衡樹算法

就是x旋轉到他父親節點的位置

void rotates(int x) //這個也就是平衡樹的旋轉,這樣旋轉后的話是不影響中序遍歷結果的
{
    int fx=f[x];
    int ffx=f[fx];
    int which=get(x);
    pushdown(fx);
    pushdown(x);
    ch[fx][which]=ch[x][which^1];
    f[ch[fx][which]]=fx;

    ch[x][which^1]=fx;
    f[fx]=x;

    f[x]=ffx;
    if(ffx) ch[ffx][ch[ffx][1]==fx]=x;
    pushup(fx);
    pushup(x);
}

 

Splay操作,就是加了一個目的地,讓x旋轉到goal這個位置

void splay(int x,int goal)  //這個就是提供了一個旋轉的終點,將樹上面x的點轉移到樹上goal這個位置
{
    for(int fx; (fx=f[x])!=goal; rotates(x))
    {
        if(f[fx]!=goal)
            rotates((get(x)==get(fx))?fx:x);
    }
    if(!goal) rt=x;
}

 

若要翻轉[l+1, r+1],將r+2 Splay到根,將l Splay到 r+2 的左兒子,然后[l+1, r+1]就在根節點的右子樹的左子樹位置了,給它打上標記

《1》、先使l旋轉到根

 

 《2》、使r+2旋轉到根

 

 由於l < r+2,此時l成了r+2的左子樹,那么r+2的右子樹的左子樹即為所求得區間,我們就可以對這棵子樹隨意操作了!比如刪除整個區間,區間內的每個數都加上x,區間翻轉,區間旋轉等。

 

 

四、例題

P3391 【模板】文藝平衡樹

 

題目描述

您需要寫一種數據結構(可參考題目標題),來維護一個有序數列。

其中需要提供以下操作:翻轉一個區間,例如原有序序列是 5 4 3 2 15\ 4\ 3\ 2\ 15 4 3 2 1,翻轉區間是 [2,4][2,4][2,4] 的話,結果是 5 2 3 4 15\ 2\ 3\ 4\ 15 2 3 4 1。

輸入格式

第一行兩個正整數 n,mn,mn,m,表示序列長度與操作個數。序列中第 iii 項初始為 iii。
接下來 mmm 行,每行兩個正整數 l,rl,rl,r,表示翻轉的區間。

輸出格式

輸出一行 nnn 個正整數,表示原始序列經過 mmm 次變換后的結果。

輸入輸出樣例

輸入 #1
5 3
1 3
1 3
1 4
輸出 #1
4 3 2 1 5

說明/提示

【數據范圍】
對於 100%100\%100% 的數據,1≤n,m≤1000001 \le n, m \leq 100000 1n,m100000,1≤l≤r≤n1 \le l \le r \le n1lrn。

 

代碼:

  1 #include<stdio.h>
  2 #include<string.h>
  3 #include<algorithm>
  4 #include<iostream>
  5 using namespace std;
  6 const int maxn=1e5+10;
  7 const int INF=1e9;
  8 int f[maxn],cnt[maxn],ch[maxn][2],sizes[maxn],key[maxn],tag[maxn],sz,rt;
  9 int n,m,x,y,data[maxn];
 10 bool get(int x)
 11 {
 12     return ch[f[x]][1]==x;
 13 }
 14 void pushup(int x)
 15 {
 16     sizes[x]=sizes[ch[x][0]]+sizes[ch[x][1]]+1;
 17 //這個是文藝平衡樹
 18     /*
 19     a[5]={5,4,3,1,2};那么存入伸展樹后,再中序遍歷的結果應該還是:{5,4,3,1,2}。
 20     即下標從小到大,而不是里面的值從小到大!這是與SBT樹(平衡樹)最大的不同!
 21     文藝平衡樹經過rotates旋轉后,它的中序遍歷結果是不變的
 22     但是讓這棵樹的一部分區間倒置之后。這個中序遍歷結果就不是遞增的了(所以它不是平衡樹)
 23     */
 24 }
 25 void pushdown(int x)
 26 {
 27     if(x && tag[x])
 28     {
 29 //這個tag標記就是用來看這個子樹用不用交換(他的交換也就對應着區間的翻轉)
 30         tag[ch[x][0]]^=1;
 31         tag[ch[x][1]]^=1;
 32         swap(ch[x][0],ch[x][1]);
 33         tag[x]=0;
 34     }
 35 }
 36 void rotates(int x) //這個也就是平衡樹的旋轉,這樣旋轉后的話是不影響中序遍歷結果的
 37 {
 38     int fx=f[x];
 39     int ffx=f[fx];
 40     int which=get(x);
 41     pushdown(fx);
 42     pushdown(x);
 43     ch[fx][which]=ch[x][which^1];
 44     f[ch[fx][which]]=fx;
 45 
 46     ch[x][which^1]=fx;
 47     f[fx]=x;
 48 
 49     f[x]=ffx;
 50     if(ffx) ch[ffx][ch[ffx][1]==fx]=x;
 51     pushup(fx);
 52     pushup(x);
 53 }
 54 void splay(int x,int goal)  //這個就是提供了一個旋轉的終點,將樹上面x的點轉移到樹上goal這個位置
 55 {
 56     for(int fx; (fx=f[x])!=goal; rotates(x))
 57     {
 58         if(f[fx]!=goal)
 59             rotates((get(x)==get(fx))?fx:x);
 60     }
 61     if(!goal) rt=x;
 62 }
 63 int build_tree(int fx,int l,int r)
 64 {
 65     //用所給數組data中數據建一棵樹(就是普通線段樹建樹)
 66     if(l>r) return 0;
 67     int mid=(l+r)>>1;
 68     int now=++sz;
 69     key[now]=data[mid];
 70     f[now]=fx;
 71     tag[now]=0;
 72 
 73     ch[now][0]=build_tree(now,l,mid-1);
 74     ch[now][1]=build_tree(now,mid+1,r);
 75     pushup(now);
 76     return now;
 77 }
 78 int kth(int x)  //這個就是獲取原輸入數組中第x個元素在樹上的位置
 79 {
 80     int now=rt;
 81     while(1)
 82     {
 83         pushdown(now);
 84         if(x<=sizes[ch[now][0]]) now=ch[now][0];
 85         else
 86         {
 87             x-=sizes[ch[now][0]]+1;
 88             if(!x) return now;
 89             now=ch[now][1];
 90         }
 91     }
 92 }
 93 void turn(int l,int r) //將區間[l,r]翻轉
 94 {
 95     l=kth(l);
 96     r=kth(r+2);
 97     splay(l,0);
 98     splay(r,l);
 99     pushdown(rt);
100     tag[ch[ch[rt][1]][0]]^=1; //根的右子樹的左子樹
101 }
102 void write(int now)
103 {
104     //按照中序遍歷輸出序列
105     pushdown(now);
106     if(ch[now][0]) write(ch[now][0]);
107     if(key[now]!=-INF && key[now]!=INF) printf("%d ",key[now]);
108     if(key[ch[now][1]]) write(ch[now][1]);
109 }
110 int main()
111 {
112     scanf("%d%d",&n,&m);
113     for (int i=1; i<=n; i++) data[i+1]=i;
114     data[1]=-INF;
115     data[n+2]=INF;
116     rt=build_tree(0,1,n+2);
117     for (int i=1; i<=m; i++)
118     {
119         scanf("%d%d",&x,&y);
120         turn(x,y);
121     }
122     write(rt);
123     return 0;
124 }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM