【bzoj4825】[Hnoi2017]單旋 線段樹+STL-set

題目描述數據結構

H 國是一個熱愛寫代碼的國家,那裏的人們很小去學校學習寫各類各樣的數據結構。伸展樹(splay)是一種數據結構,由於代碼好寫,功能多,效率高,掌握這種數據結構成爲了 H 國的必修技能。有一天,邪惡的「卡」帶着他的邪惡的「常數」來企圖毀滅 H 國。「卡」給 H 國的人洗腦說,splay 若是寫成單旋的,將會更快。「卡」稱「單旋 splay」爲「spaly」。雖然說他說的很沒道理,但仍是有 H 國的人相信了,小 H 就是其中之一,spaly 立刻成爲他的信仰。 而 H 國的國王,天然不容許這樣的風氣蔓延,國王構造了一組數據,數據由 m 個操做構成,他知道這樣的數據確定打垮 spaly,可是國王還有不少不少其餘的事情要作,因此統計每一個操做所須要的實際代價的任務就交給你啦。
數據中的操做分爲五種:
1. 插入操做:向當前非空 spaly 中插入一個關鍵碼爲 key 的新孤立節點。插入方法爲,先讓 key 和根比較,若是 key 比根小,則往左子樹走,不然往右子樹走,如此反覆,直到某個時刻,key 比當前子樹根 x 小,而 x 的左子樹爲空,那就讓 key 成爲 x 的左孩子; 或者 key 比當前子樹根 x 大,而 x 的右子樹爲空,那就讓 key 成爲 x 的右孩子。該操做的代價爲:插入後,key 的深度。特別地,若樹爲空,則直接讓新節點成爲一個單個節點的樹。(各節點關鍵碼互不相等。對於「深度」的解釋見末尾對 spaly 的描述)。
2. 單旋最小值:將 spaly 中關鍵碼最小的元素 xmin 單旋到根。操做代價爲:單旋前 xmin 的深度。(對於單旋操做的解釋見末尾對 spaly 的描述)。
3. 單旋最大值:將 spaly 中關鍵碼最大的元素 xmax 單旋到根。操做代價爲:單旋前 xmax 的深度。
4. 單旋刪除最小值:先執行 2 號操做,而後把根刪除。因爲 2 號操做以後,根沒有左子樹,因此直接切斷根和右子樹的聯繫便可(具體見樣例解釋)。 操做代價同 2 號操 做。
5. 單旋刪除最大值:先執行 3 號操做,而後把根刪除。 操做代價同 3 號操做。
 
對於不是 H 國的人,你可能須要瞭解一些 spaly 的知識,才能完成國王的任務:
a. spaly 是一棵二叉樹,知足對於任意一個節點 x,它若是有左孩子 lx,那麼 lx 的關鍵碼小於 x 的關鍵碼。若是有右孩子 rx,那麼 rx 的關鍵碼大於 x 的關鍵碼。
b. 一個節點在 spaly 的深度定義爲:從根節點到該節點的路徑上一共有多少個節點(包括本身)。
c. 單旋操做是對於一棵樹上的節點 x 來講的。一開始,設 f 爲 x 在樹上的父親。若是 x 爲 f 的左孩子,那麼執行 zig(x) 操做(如上圖中,左邊的樹通過 zig(x) 變爲了右邊的樹),不然執行 zag(x) 操做(在上圖中,將右邊的樹通過 zag(f) 就變成了左邊的樹)。每當執 行一次 zig(x) 或者 zag(x),x 的深度減少 1,如此反覆,直到 x 爲根。總之,單旋 x 就是經過反覆執行 zig和 zag 將 x 變爲根。

輸入學習

第一行單獨一個正整數 m。
接下來 m 行,每行描述一個操做:首先是一個操做編號 c∈[1,5],即問題描述中給出的五種操做中的編號,若 c = 1,則再輸入一個非負整數 key,表示新插入節點的關鍵碼。
1≤m≤10^5,1≤key≤10^9
全部出現的關鍵碼互不相同。任何一個非插入操做,必定保證樹非空。在未執行任何操做以前,樹爲空

輸出spa

輸出共 m 行,每行一個整數,第 i 行對應第 i 個輸入的操做的代價。

樣例輸入blog

5
1 2
1 1
1 3
4
5
it

樣例輸出io

1
2
2
2
2
class


題解效率

Splay 線段樹+STL-setdate

這道題的關鍵之處在於:除了插入之外,只操做最大/最小值。二叉樹

因此每次旋轉只有一個固定方向。

以旋轉最小值爲例,必定是不斷的進行zig操做最終旋轉到根。

經過找規律能夠證實這種操做以後,最小節點深度變爲1,原來最小節點的右子樹深度不變,其他節點深度+1而且結構不變。

而右子樹必定是一段連續的區間,這樣咱們可使用線段樹維護這個過程,同時還要維護每一個節點的父親和兒子,這個不難維護。

刪除的時候,把根節點刪掉,其他節點深度-1且結構不變。

這樣2345操做都搞定了,只剩下1操做。

考慮到插入一個數x,必定是在它的前驅pro或後繼sub其一插入,並且必定存在某個節點不存在相應的兒子。

具體地,有個結論:pro和sub的深度必定不一樣,且深度大的能夠插入。

具體證實很簡單,在這裏不細講了。

這樣只要知道前驅後繼便可,能夠在線段樹上求,但我懶了直接上set搞定。

#include <cstdio>
#include <algorithm>
#include <set>
#define N 100010
#define lson l , mid , x << 1
#define rson mid + 1 , r , x << 1 | 1
using namespace std;
set<int> s;
set<int>::iterator it , pro , sub;
int sum[N << 2] , si[N << 2] , tag[N << 2] , ls[N] , rs[N] , fa[N] , opt[N] , a[N] , v[N] , tot , root;
void pushup(int x)
{
    sum[x] = sum[x << 1] + sum[x << 1 | 1] , si[x] = si[x << 1] + si[x << 1 | 1];
}
void pushdown(int x)
{
    if(tag[x])
    {
        sum[x << 1] += si[x << 1] * tag[x] , tag[x << 1] += tag[x];
        sum[x << 1 | 1] += si[x << 1 | 1] * tag[x] , tag[x << 1 | 1] += tag[x];
        tag[x] = 0;
    }
}
void change(int p , int o , int d , int l , int r , int x)
{
    if(l == r)
    {
        si[x] += o , sum[x] += d;
        return;
    }
    pushdown(x);
    int mid = (l + r) >> 1;
    if(p <= mid) change(p , o , d , lson);
    else change(p , o , d , rson);
    pushup(x);
}
void update(int b , int e , int a , int l , int r , int x)
{
    if(b <= l && r <= e)
    {
        sum[x] += a * si[x] , tag[x] += a;
        return;
    }
    pushdown(x);
    int mid = (l + r) >> 1;
    if(b <= mid) update(b , e , a , lson);
    if(e > mid) update(b , e , a , rson);
    pushup(x);
}
int query(int p , int l , int r , int x)
{
    if(l == r) return sum[x];
    pushdown(x);
    int mid = (l + r) >> 1;
    if(p <= mid) return query(p , lson);
    else return query(p , rson);
}
int main()
{
    int m , i , d;
    scanf("%d" , &m);
    for(i = 1 ; i <= m ; i ++ )
    {
        scanf("%d" , &opt[i]);
        if(opt[i] == 1) scanf("%d" , &a[i]) , v[++tot] = a[i];
    }
    sort(v + 1 , v + tot + 1);
    for(i = 1 ; i <= m ; i ++ ) if(opt[i] == 1) a[i] = lower_bound(v + 1 , v + tot + 1 , a[i]) - v;
    for(i = 1 ; i <= m ; i ++ )
    {
        if(opt[i] == 1)
        {
            if(s.empty()) root = a[i] , d = 1;
            else
            {
                it = s.upper_bound(a[i]) , sub = it;
                if(sub == s.begin()) ls[*sub] = a[i] , fa[a[i]] = *sub , d = query(*sub , 1 , tot , 1) + 1;
                else
                {
                    pro = --it;
                    if(sub == s.end()) rs[*pro] = a[i] , fa[a[i]] = *pro , d = query(*pro , 1 , tot , 1) + 1;
                    else if(query(*sub , 1 , tot , 1) < query(*pro , 1 , tot , 1)) rs[*pro] = a[i] , fa[a[i]] = *pro , d = query(*pro , 1 , tot , 1) + 1;
                    else ls[*sub] = a[i] , fa[a[i]] = *sub , d = query(*sub , 1 , tot , 1) + 1;
                }
            }
            printf("%d\n" , d);
            change(a[i] , 1 , d , 1 , tot , 1);
            s.insert(a[i]);
        }
        else if(opt[i] % 2 == 0)
        {
            it = s.begin() , d = query(*it , 1 , tot , 1);
            printf("%d\n" , d);
            if(*it != root)
            {
                update(1 , tot , 1 , 1 , tot , 1);
                update(*it , *it , -d , 1 , tot , 1);
                int t = fa[*it];
                ls[t] = fa[*it] = 0;
                if(rs[*it]) update(*it + 1 , t - 1 , -1 , 1 , tot , 1) , fa[rs[*it]] = t , ls[t] = rs[*it];
                rs[*it] = root , fa[root] = *it , root = *it;
            }
            if(opt[i] == 4) root = rs[*it] , rs[*it] = fa[root] = 0 , change(*it , -1 , -1 , 1 , tot , 1) , update(1 , tot , -1 , 1 , tot , 1) , s.erase(*it);
        }
        else
        {
            it = s.end() , it -- , d = query(*it , 1 , tot , 1);
            printf("%d\n" , d);
            if(*it != root)
            {
                update(1 , tot , 1 , 1 , tot , 1);
                update(*it , *it , -d , 1 , tot , 1);
                int t = fa[*it];
                rs[t] = fa[*it] = 0;
                if(ls[*it]) update(t + 1 , *it - 1 , -1 , 1 , tot , 1) , fa[ls[*it]] = t , rs[t] = ls[*it];
                ls[*it] = root , fa[root] = *it , root = *it;
            }
            if(opt[i] == 5) root = ls[*it] , ls[*it] = fa[root] = 0 , change(*it , -1 , -1 , 1 , tot , 1) , update(1 , tot , -1 , 1 , tot , 1) , s.erase(*it);
        }
    }
    return 0;
}
相關文章
相關標籤/搜索