1. 程式人生 > >【題解】洛谷P3391【模板】文藝平衡樹 splay

【題解】洛谷P3391【模板】文藝平衡樹 splay

【階梯報告】洛谷P3391【模板】文藝平衡樹 splay

題目連結在這裡連結
最近在學習splay,終於做對了這道模版題,雖然不是很難的樣子。但是我一開始並不會做,而且看完題解之後還打錯一直打不對,除錯了很久
下面是題目簡述

    現在給你一個長度為n的序列,序列元素初始為1,2,3...n,同時有m個操作,每個操作給定一個L和R,表示將[L,R]區間的數進行翻轉。
    輸出:完成所有操作之後的序列(n,m≤100000)

首先,這道題用暴力是肯定超時的。但是既然連題目都提示用splay做這道題了,那麼我們自然可以用splay做這道題。
首先我們想一下splay的樹的性質。

性質

  1. 如果我們給每一個點新增一個鍵值,這個鍵值表示的是這個點在序列中的位置,那麼我們對splay樹進行中序遍歷之後,得到的序列就是原來對應的序列。
  2. 對於splay樹,無論是進行zip還是zap操作,都不會影響最終中序遍歷的結果。因為zip和zap都是在保證二叉查詢樹的性質的前提下進行的。
  3. 假如我們splay(L-1,root),然後,splay(R+1,root->right_child),那麼我們就會驚奇地發現,區間[L,R]內的所有點都在R+1這個結點的左子樹種。
  4. 假如我們把任意一個結點的子樹中的每一個結點的左右子樹都換邊,(包括自己)那麼最終中序遍歷的結果就是——除了被翻轉的子樹以外,其他所有點的中序遍歷都沒有變化,而子樹中的所有節點(包括子樹根節點)的中序遍歷剛剛好反了過來。(你們可以自己畫一個圖試一下,或者說,如下圖)
    在這裡插入圖片描述

    在這裡插入圖片描述
    在這裡插入圖片描述

思路

第一種簡陋的想法
  1. 以1,2,3,4…n為鍵值插入splay樹(或者直接構建一棵平衡樹就行,因為你已經知道會有哪些元素插入樹中)。之後,我們對樹的所有操作都不需要用到鍵值,直接根據zip和zap不會改變中序遍歷的特性進行改變。
  2. 翻轉區間的時候,先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. 翻轉樹的時候不需要把整個子樹都交換,只要採取線段樹的懶標記的方法做一個標記即可。
  • 標記完之後,假如等一下再標記同樣的區間,只要異或一下1就可以了
  • 假如要splay的點在已經翻轉過的子樹裡面,那麼我們到時候只要在尋找結點的過程中進行push_down就可以了。
  • 在輸出整棵splay樹的時候,也注意一下push_down就可以了。

具體方法

  1. 把所有的元素都塞進splay裡面。由於一開始就是有序的,所以你既可以一個一個地insert進去,也可以直接就構建一個十分平衡的splay樹
  2. 對於每一個翻轉操作,首先splay(l-1,root),splay(r+1,root->right_child),然後再把r+1的左子樹打上標記即可
  3. 要點:每次在find一個結點的時候,一定要先push_down,然後再進行操作。
  4. 最後輸出整棵樹的時候也注意一下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不讓發水貼,所以以後我有什麼要說的就在文章的後面說一些吧)