【題解】洛谷P3391【模板】文藝平衡樹 splay
阿新 • • 發佈:2018-12-22
【階梯報告】洛谷P3391【模板】文藝平衡樹 splay
題目連結在這裡連結
最近在學習splay,終於做對了這道模版題,雖然不是很難的樣子。但是我一開始並不會做,而且看完題解之後還打錯一直打不對,除錯了很久
下面是題目簡述
現在給你一個長度為n的序列,序列元素初始為1,2,3...n,同時有m個操作,每個操作給定一個L和R,表示將[L,R]區間的數進行翻轉。
輸出:完成所有操作之後的序列(n,m≤100000)
首先,這道題用暴力是肯定超時的。但是既然連題目都提示用splay做這道題了,那麼我們自然可以用splay做這道題。
首先我們想一下splay的樹的性質。
性質
- 如果我們給每一個點新增一個鍵值,這個鍵值表示的是這個點在序列中的位置,那麼我們對splay樹進行中序遍歷之後,得到的序列就是原來對應的序列。
- 對於splay樹,無論是進行zip還是zap操作,都不會影響最終中序遍歷的結果。因為zip和zap都是在保證二叉查詢樹的性質的前提下進行的。
- 假如我們splay(L-1,root),然後,splay(R+1,root->right_child),那麼我們就會驚奇地發現,區間[L,R]內的所有點都在R+1這個結點的左子樹種。
- 假如我們把任意一個結點的子樹中的每一個結點的左右子樹都換邊,(包括自己)那麼最終中序遍歷的結果就是——除了被翻轉的子樹以外,其他所有點的中序遍歷都沒有變化,而子樹中的所有節點(包括子樹根節點)的中序遍歷剛剛好反了過來。(你們可以自己畫一個圖試一下,或者說,如下圖)
思路
第一種簡陋的想法
- 以1,2,3,4…n為鍵值插入splay樹(或者直接構建一棵平衡樹就行,因為你已經知道會有哪些元素插入樹中)。之後,我們對樹的所有操作都不需要用到鍵值,直接根據zip和zap不會改變中序遍歷的特性進行改變。
- 翻轉區間的時候,先splay(l-1,root),splay(r+1,root->right_child),然後再把r+1這個結點的左子樹的所有左右孩子交換即可。
雖然這麼做確實可以做對題目,但是這樣會有幾個需要注意的地方。
1 . 假如要reserve(1,n),那麼我們將會splay(0,root)和splay(n+1,root->left_child),但是樹中沒有0和n+1這兩個結點。因此一開始的時候我們就應該加入INF和-INF,充當衛兵的作用。
2 . 交換子樹中所有結點的速度實在是太慢了,和o(n)壓根沒有區別。這樣根本就沒有變快。
優化的方法
- 翻轉樹的時候不需要把整個子樹都交換,只要採取線段樹的懶標記的方法做一個標記即可。
- 標記完之後,假如等一下再標記同樣的區間,只要異或一下1就可以了
- 假如要splay的點在已經翻轉過的子樹裡面,那麼我們到時候只要在尋找結點的過程中進行push_down就可以了。
- 在輸出整棵splay樹的時候,也注意一下push_down就可以了。
具體方法
- 把所有的元素都塞進splay裡面。由於一開始就是有序的,所以你既可以一個一個地insert進去,也可以直接就構建一個十分平衡的splay樹
- 對於每一個翻轉操作,首先splay(l-1,root),splay(r+1,root->right_child),然後再把r+1的左子樹打上標記即可
- 要點:每次在find一個結點的時候,一定要先push_down,然後再進行操作。
- 最後輸出整棵樹的時候也注意一下push_down
下面是我的程式碼,可能不是很看得明白。
#include<cstdio>
using namespace std;
const int N=100010;
const int INF=100000000;
struct node
{
int lc,rc,fa,size,val,mark;//mark就是懶標記,val是本來的值,size是子樹(包括自己)的大小
node()
{
lc=rc=fa=size=val=mark=0;
}
}tree[N];
int root=1,tot=1,FIRST=1;
void build(int,int,int,int);//直接用遞迴在剛開始的時候建立一棵比較平衡的樹
void push_down(int);
void zip(int);//左旋
void zap(int);//右旋
void initailize();//放入INT和-INF作為衛兵
void splay(int,int);
int find(int);//用於尋找splay樹中的第幾大的數。畢竟在序列中排第k的數就是在splay中第k大的數
void reverse(int,int);
void print(int);//用遞迴輸出整棵樹
int main()
{
int n,m;
scanf("%d%d",&n,&m);
build(1,1,n,0);
initailize();
while(m--)
{
int l,r;
scanf("%d%d",&l,&r);
reverse(l+1,r+1);//由於有-INF的存在,所以翻轉[l,r]區間的時候,其實是翻轉[l+1,r+1]區間
}
print(root);
return 0;
}
void build(int x,int l,int r,int father)
{
tree[x].fa=father;
tree[x].size=1;
if(l==r)
{
tree[x].val=l;
return;
}
int mid=(l+r)/2;
tree[x].val=mid;
if(mid==l)
{
tree[x].rc=++tot;
build(tot,r,r,x);
tree[x].size+=tree[tree[x].rc].size;
}
else
{
tree[x].lc=++tot;
build(tot,l,mid-1,x);
tree[x].rc=++tot;
build(tot,mid+1,r,x);
tree[x].size+=tree[tree[x].lc].size+tree[tree[x].rc].size;
}
}
void initailize()
{
int p=root;
while(tree[p].lc!=0) tree[p].size++,p=tree[p].lc;
tree[p].size++;
tree[p].lc=++tot;
tree[tot].fa=p;
tree[tot].size=1;
tree[tot].val=-INF;
p=root;
while(tree[p].rc!=0) tree[p].size++,p=tree[p].rc;
tree[p].size++;
tree[p].rc=++tot;
tree[tot].fa=p;
tree[tot].size=1;
tree[tot].val=INF;
}
void inline push_down(int x)
{
if(tree[x].mark)
{
int L=tree[x].lc,R=tree[x].rc;
tree[L].mark^=1;
tree[R].mark^=1;
tree[x].mark=0;
tree[x].lc=R;
tree[x].rc=L;
}
}
void zip(int x)
{
int y=tree[x].fa;
tree[y].rc=tree[x].lc;
tree[x].lc=y;
tree[x].fa=tree[y].fa;
tree[y].fa=x;
if(tree[x].fa)
if(tree[tree[x].fa].lc==y)
tree[tree[x].fa].lc=x;
else tree[tree[x].fa].rc=x;
else root=x;
if(tree[y].rc)
tree[tree[y].rc].fa=y;
tree[y].size=tree[tree[y].lc].size+tree[tree[y].rc].size+1;
tree[x].size=tree[tree[x].lc].size+tree[tree[x].rc].size+1;
}
void zap(int x)
{
int y=tree[x].fa;
tree[y].lc=tree[x].rc;
tree[x].rc=y;
tree[x].fa=tree[y].fa;
tree[y].fa=x;
if(tree[x].fa)
if(tree[tree[x].fa].lc==y)
tree[tree[x].fa].lc=x;
else tree[tree[x].fa].rc=x;
else root=x;
if(tree[y].lc)
tree[tree[y].lc].fa=y;
tree[y].size=tree[tree[y].lc].size+tree[tree[y].rc].size+1;
tree[x].size=tree[tree[x].lc].size+tree[tree[x].rc].size+1;
}
void splay(int x,int aim)
{
aim=tree[aim].fa;
while(tree[x].fa!=aim)
{
int y=tree[x].fa;
int z=tree[y].fa;
if(z==aim)
if(tree[y].lc==x)
zap(x);
else zip(x);
else if(tree[z].lc==y&&tree[y].lc==x)
zap(x),zap(x);
else if(tree[z].rc==y&&tree[y].rc==x)
zip(x),zip(x);
else if(tree[z].lc==y)
zip(x),zap(x);
else zap(x),zip(x);
}
}
int find(int k)
{
int p=root;
while(1)
{
push_down(p);
if(tree[tree[p].lc].size>=k)
p=tree[p].lc;
else
{
k-=tree[tree[p].lc].size;
if(k==1) return p;
k-=1;
p=tree[p].rc;
}
}
}
void reverse(int l,int r)
{
int L=find(l-1);
splay(L,root);
int R=find(r+1);
splay(R,tree[L].rc);
tree[tree[R].lc].mark^=1;
}
void print(int x)
{
if(x==0) return;
push_down(x);
print(tree[x].lc);
if(tree[x].val!=INF&&tree[x].val!=-INF)
{
if(FIRST)
{
FIRST=0;
printf("%d",tree[x].val);
}
else printf(" %d",tree[x].val);
}
print(tree[x].rc);
}
對了,順便說一下,為什麼我看的資料書裡面,不同的平衡樹的zip是不一樣的?有些是表示左旋,有些是表示右旋,zap也是一樣,導致我現在都是按照自己的標準來了。
(對了,貌似CSDN不讓發水貼,所以以後我有什麼要說的就在文章的後面說一些吧)