我們嘗試用序列之王spaly來解決線段樹經典兩個問題:
最大值和最大值2。
在n(1<=n<=100000)個數a1…an組成的序列上進行m(1<=m<=100000)次操作,操作有兩種:
1 x y:表示修改a[x]為y;
2 x y:詢問x到y之間的最大值。
這是線段樹很容易解決的問題。
我們現在考慮用splay解決。
我們設key[i]表示結點i的值。
那麼初始情況下key[i]=a[i]。
設num[i]表示以結點i為根的子樹中的最大值。
我們將n個結點弄成一條鏈。
然後旋最小的至根進行平衡調整。
接下來我們可以打出所有splay基本操作:
pd(x):判斷x是否為其父親的右子樹。
updata(x):更新結點x的值(例如num就需要更新)。
rotate(x):將結點x往上旋(需要用到函式pd)。
splay(x,y):將結點x旋至結點y下方(如果y=0相當於旋到頂)。
kth(x,y):在以結點x為根的子樹中查詢第y小的結點。
split(x,y,l,r):在以結點x為根的子樹中(注意father[x]為0),從第y小的元素後分開。讓前y個元素變為一棵splay,其餘變為一棵splay,其中前者根節點賦值給l,後者根節點賦值給r。
merge(l,r,x):將以結點l為根的splay和以結點r為根的splay合併成一棵splay(father[l]=0,father[r]=0),得到的新splay根節點賦值給x。
為了支援kth,merge操作,我們需要維護size[i]表示以i為根的子樹中有多少結點。
為了更好更優美的執行split和merge操作[即使**後兩棵splay均非空],我們在第乙個元素新增虛擬元素[虛擬結點]n+1,在最後乙個元素後新增虛擬元素[虛擬結點]n+2。
這些操作的實現不講,不懂的可以去學習然後看一下參考程式的實現。
注意的是虛擬結點和空結點的num值和key值都得賦值為-inf。
#include
#include
#include
#define fo(i,a,b) for(i=a;i<=b;i++)
#define maxn (100000+5)
#define inf (int_max)
using
namespace
std;
int tree[maxn][3],father[maxn],key[maxn],num[maxn],size[maxn];
int i,j,k,l,r,mid,t,n,m,tot,root,x,y;
int pd(int x)
void updata(int x)
void rotate(int x)
void splay(int x,int y)
}int kth(int x,int y)
void split(int x,int y,int &l,int &r)
void merge(int l,int r,int &x)
int main()
else
}tree[n+2][0]=n;
father[n]=n+2;
updata(n+2);
root=n+2;
splay(n+1,0);
root=n+1;
scanf("%d",&m);
fo(i,1,m)
else
}}
這次要求我們支援區間修改(區間都加上乙個數,可以是負數),怎麼辦?
我們學習線段樹,可以打懶標記。
因此add[i]表示以i結點為根的splay全部加上add[i]。
當乙個j需要進行splay操作時,沿途的結點就進行懶標記下傳。
那麼我們只需要在第一題的基礎上加兩個操作:
remove(x,y):在進行splay(x,y)時,對沿途的結點進行懶標記下傳。
clear(x):對結點x進行懶標記下傳。
然後我們發現並不能對,在答案為負數的情況下會輸出0。
? 其實,是因為虛擬結點它是真實不存在的,因此你不能改變它的key值。而在虛擬結點沒有左右子樹時,也不能改變它的num值。
特殊處理一下即可。具體看程式實現。
為什麼第一題不會出現這種情況?因為第一題不需要區間修改,就沒有懶標記下傳。虛擬結點的key永遠不會被修改。
#include
#include
#include
#include
#define fo(i,a,b) for(i=a;i<=b;i++)
#define maxn (100000+5)
#define inf (int_max)
using
namespace
std;
stack
s;int tree[maxn][3],father[maxn],key[maxn],num[maxn],size[maxn],add[maxn];
int i,j,k,l,r,mid,t,n,m,tot,root,x,y;
int pd(int x)
void updata(int x)
void rotate(int x)
void clear(int x)
if (tree[x][1])
add[x]=0;
}}void remove(int x,int y)while (x!=y);
while (!s.empty())
}void splay(int x,int y)
}int kth(int x,int y)
void split(int x,int y,int &l,int &r)
void merge(int l,int r,int &x)
int main()
else
}tree[n+2][0]=n;
father[n]=n+2;
updata(n+2);
root=n+2;
splay(n+1,0);
root=n+1;
scanf("%d",&m);
fo(i,1,m)
else
}}
實際上splay還可以做很多線段樹做不了的東西。
例如翻轉操作(排序機械臂)。
可以維護連通性(網路通訊)。
序列之王,維護序列(維護序列是一道題目)。
大家可以去做。
序列 線段樹
使用線段樹維護 b bb,初值為 bi b i bi 每次修改時,若乙個位置上的值變為了 0 00,則說明其會對答案產生新的貢獻,在外部使用樹狀陣列將貢獻計入答案,然後將該位置的值 重置為 bi b i bi 重置的時間複雜度是 o log n o log n o logn 考慮最壞情況,每次操作 ...
序列操作(線段樹)
lxhgww 最近收到了乙個 01 序列,序列裡面包含了 n 1 n 105 個數,這些書要麼是 0,要麼是 1,現在對這個序列有五種變換操作和詢問操作 0 a b 把 a,b 區間內所有數全部變成 0。1 a b 把 a,b 區間內所有數全部變成 1。2 a b 把 a,b 區間內所有數全部取反,...
序列操作 線段樹
題目大意 你要維護乙個長度為n的序列,進行操作。對於這個序列,233之類的數不能出現,也就是說233,2333,23333,233333 這一系列的數不能在序列中出現。1 i 輸出第i個元素。2 a b x 將 a,b 區間的序列賦值為x。3 a b x 將 a,b 區間的序列加上x。4 a b 將...