區間樹Splay NOI2005 維護數列

2022-05-25 12:48:11 字數 4317 閱讀 9965

區間樹這玩意真tm玄學。

1.通過【模板】文藝平衡樹(splay),【模板】普通平衡樹,gss3 - can you answer these queries iii

2.學會splay,學會求最大子段和並知道怎麼維護資訊和下傳標記,及會有區間修的最大子段和

3.多年的程式設計技巧,以及一顆寫資料結構的良好心態

4.攢夠兩個月的肝,這很重要!

1.看以下部落格splay入門解析,文藝平衡樹splay題解,gss系列題解——最大子段和系列。

2.看上面

3.別管,瞎逼的

4.好好養生,如果不夠肝的話千萬別寫這道題

既然你已經會了上面的前置技能,那麼我們就可以開始分步解決這道題了。

先給出我們需要存的全部資訊:

struct kkk	//清空節點資訊

}tree[maxn];

存的東西很多,大家務必要理解清楚每乙個資訊所表達的含義。

做過「普通平衡樹」的都知道,在「普通平衡樹」裡,splay是按照權值來排序的,所以能維護數的關係。那麼現在到了維護區間上的操作了,也就不能按權值來排序了。

區間樹,我們按照的是序列中的編號來排序。

我們可以發現,序列中的第k個點,在splay中也是第k大的。(按編號排序嘛

所以我們想要查詢序列中第k個位置,就直接找splay中的第k大就可以了。

所以「普通平衡樹」裡的splay操作,rotate操作和kth操作都是可以直接照搬的(一樣的,只是維護編號而已

那麼我們怎麼在splay中找到乙個區間[x,y]呢?

我們可以考慮splay的性質,將xsplay上根,再將ysplay上到x的右節點,那麼我們得出的y的左子樹就是我們要的[x,y]區間。

之後我們想對這個區間做什麼就可以直接對那顆子樹做了。

上面就是區間樹的一些介紹

#define tagnone 10000001				//沒有賦值tag的標誌

#define l(node) (tree[node].ch[0]) //替左兒子

#define r(node) (tree[node].ch[1]) //替右兒子

#define f(node) (tree[node].fa) //替父親

#define v(node) (tree[node].val) //替權值

#define s(node) (tree[node].size) //替子樹大小

#define compare(node,x) (tree[node].val這個就不用怎麼說了吧,大家在做平衡樹splay都寫過的啦!

和上面講的區間樹一樣,先找到區間[l,r]的kth,計l的kth為xr的kth為y

然後splay(x,0);splay(y,x); (直接上**解釋)

最後返回y左兒子就是指定區間

**:int split(int k,int len)

一開始我們要構造一顆有初始資訊的splay,乙個乙個insert顯然很慢,所以我們寫乙個build,可以將一段序列建成一顆平衡的splay的操作。

其實寫起來和線段樹差不多,注意是以編號排序來建樹。

void new(int node,int x)

void build(int begin,int end,int fa)

這裡題目要求的是在x位置後插入一段長為len的序列

如果我們還是乙個乙個插入,仍然很慢,所以我們可以直接把插入的序列build成一顆平衡的子樹,最後直接在x後插入建成的子樹就可以了。

void insert(int k,int len)
這個就更簡單了,直接找到那個區間,然後讓那個子樹的父親將左兒子清為0就可以了。

但是,為了節省空間,我們加入了乙個垃圾**的操作,就是將刪除的節點重新利用起來,以節省空間

所以我們還要遍歷一遍子樹將那顆子樹的節點扔進垃圾桶裡

int rublish()

void remove(int node)

void eraser(int x,int len)

一樣的,先找到指定區間的子樹,然後直接修改資訊,打上賦值標記

void change_val(int node,int val)

void update(int x,int tot,int val)

一樣的,先找到指定的區間的子樹,然後直接翻轉,打上翻轉標記

void change_rev(int node)

void reverse(int x,int len)

這就更簡單了,找到指定區間的子樹,然後直接輸出那顆子樹的sum就ok了

void query(int x,int len)
直接輸出root的middle最大子段和

printf("%d\n",tree[root].middle);
這玩意是真毒瘤,不過主要還是難在最大子段和上面,只要我們能理解「gss3」中的求法,其實也很簡單。

維護資訊,所以除了這個最大子段和之外,好像還挺簡單的。最大子段和那幾個更新方法這裡就不講了,不知道可以看上面的部落格。

void pushup(int node)
下傳標記,這本來是比較得毒瘤,我們要先更新賦值操作,左右兒子有很多資訊需要更新,其中就有tag,sum,left,right和middle,更新起來十分的繁瑣。但是在之前的賦值操作update中,我們引入了乙個叫change_val的函式,所以這裡,我們可以直接呼叫那個函式。於是**就被減短了很多。

最後將tag標記為tagnone就ok了

然後要更新翻轉操作,一樣的,在之前翻轉操作revrese中,我們引入了乙個叫change_rev的函式,所以這裡,我們還是可以直接呼叫。於是**又被減了……

最後將rev標記為0就ok了。

**:

void pushdown(int node)

if(tree[node].rev)

}

看,多簡短

注意邊界!注意邊界!注意邊界! 主要的事情說三遍!

其他就沒什麼了,都是輸入嘛。

#include#define tagnone 10000001

#define maxn 1000010

#define inf 100000001

#define l(node) (tree[node].ch[0]) //替左兒子

#define r(node) (tree[node].ch[1]) //替右兒子

#define f(node) (tree[node].fa) //替父親

#define v(node) (tree[node].val) //替權值

#define s(node) (tree[node].size) //替子樹大小

#define compare(node,x) (tree[node].val>1;int node=id[mid],pre=id[fa];

if(begin==end) //到達底部

new(node,a[begin]); //新建乙個節點

if(begin=fa]=node;

}int kth(int x)

}void remove(int node)

int split(int k,int len)

void query(int x,int len)

void update(int x,int tot,int val)

void rever(int x,int len)

void eraser(int x,int len)

void insert(int k,int len)

int main()

}

學習時有參考i_am_helloword大佬的題解。所以有的地方和他的**很像。

希望大家都能掌握區間樹qwq。

線段樹(區間樹)

目錄 為什麼要使用線段樹 什麼是線段樹 線段樹融合介面 線段樹實現 線段樹例題 融合介面 author administrator param public inte ce merger package com.suanfa.segmenttree 線段樹 區間樹 author administra...

線段樹 區間樹

每乙個節點儲存的是乙個區間中相應統計值 在treeindex的位置建立表示區間 l.r 的線段樹 private void buildsegmenttree int treeindex,int l,int r int lefttreeindex leftchild treeindex int rig...

線段樹 區間加區間乘

給出序列 a1,a2,an 0 ai 109 有關序列的四種操作 1.al,al 1,ar 1 l r n 加上 x 103 x 103 2.al,al 1,ar 1 l r n 乘上 x 103 x 103 3.al,al 1,ar 1 l r n 變成 x al,x al 1 x ar 103 ...