一、文藝平衡樹解決什么問題
您需要寫一種數據結構(可參考題目標題),來維護一個有序數列。
其中需要提供以下操作:翻轉一個區間,例如原有序序列是 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,區間翻轉,區間旋轉等。
四、例題
題目描述
您需要寫一種數據結構(可參考題目標題),來維護一個有序數列。
其中需要提供以下操作:翻轉一個區間,例如原有序序列是 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 次變換后的結果。
輸入輸出樣例
5 3 1 3 1 3 1 4
4 3 2 1 5
說明/提示
【數據范圍】
對於 100%100\%100% 的數據,1≤n,m≤1000001 \le n, m \leq 100000 1≤n,m≤100000,1≤l≤r≤n1 \le l \le r \le n1≤l≤r≤n。
代碼:
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 }