BZOJ1500 NOI2005 維修數列

2022-05-08 05:27:11 字數 3806 閱讀 9697

輸入的第1 行包含兩個數n 和m(m ≤20 000),n 表示初始時數列中數的個數,m表示要進行的運算元目。

第2行包含n個數字,描述初始時的數列。

以下m行,每行一條命令,格式參見問題描述中的**。

任何時刻數列中最多含有500 000個數,數列中任何乙個數字均在[-1 000, 1 000]內。

插入的數字總數不超過4 000 000個,輸入檔案大小不超過20mbytes。

對於輸入資料中的get-sum和max-sum操作,向輸出檔案依次列印結果,每個答案(數字)佔一行。

9 82 -6 3 5 1 -5 -3 6 3

get-sum 5 4

max-sum

insert 8 3 -5 7 2

delete 12 1

make-same 3 3 2

reverse 3 6

get-sum 5 4

max-sum

-110110

正解:splay

解題報告:

簡單說一下操作的實現吧:

1、insert

操作:因為有大量元素插入,如果每個都新建結點的話空間開不下,所以我們必須要**空間,也就是說之前

delete

操作刪掉的結點我要把它**利用。因為我們是在

pos後插入若干元素,那麼我可以把

pos變成根結點,

pos+1

變成右子樹的根,那麼

pos+1

的左子樹為空,我可以先把需要插入的元素構成一棵樹,再把這棵樹插入到左子樹上,往上

update

一遍。2、

delete

操作:考慮刪除若干元素該怎麼做。因為我們想刪除一段區間

[l,r]

,那麼我們可以把

l-1旋轉到根,

r+1旋轉到根的右子樹的根,那麼此時

r+1的左子樹就是要刪除的,我們可以直接把左子樹刪掉,然後**結點。注意我需要掃一遍這需要刪除的整棵左子樹,把上面的標記什麼的全部清空。看上去這個複雜度是o(

n)的,但實際上題目中說插入元素不超過

400w

,也就是說刪除元素也不會超過這麼多,所以複雜度沒有問題。

3、make-same

操作:因為我們要把一段區間修改為乙個數,所以與上述做法類似,旋轉使得需要操作的區間處在一棵子樹中,然後給根結點打上標記,表示這個點為根結點的子樹全部修改為乙個值。每次旋轉的時候記得下傳就可以了。

4、reverse

操作:翻轉區間與

make-same

類似,給結點打上標記,表示這棵子樹需要翻轉,每次下傳即可。可以畫圖發現,每次我下傳的時候把左右子樹交換則最終可以起到翻轉的效果。

5、get-sum

操作:得到區間之後直接呼叫根結點的維護的

sum值即可。

6、max-sum

操作:我可以對於每個結點,儲存乙個它所控制的區間的字首最大值、字尾最大值和整體最大值,做法經典,不再贅述。

大概總結:

這道題的的確確是一道碼農題,但是只要思維清晰,對於splay

的操作熟悉的話,打起來也會很快。

細節很多,寫的時候一定謹慎,因為查起錯來會很麻煩,務必盡最大努力一遍寫對。想清楚再寫,想一想模板的模式,沒事的時候也可以複習複習splay

模板,也想一想自己打

splay

的時候犯過什麼錯。

( 我的查錯經過:(蒟蒻的查錯辛酸史)

錯誤一:翻轉標記下傳時,不是賦值,而是把兒子結點的標記^1;

錯誤二:求第x大的元素時忘記pushdown;

錯誤三:插入的時候,我得到pos的位置時不應該直接把pos旋轉,而應該先找到位置處在pos的元素是誰;

錯誤四:刪除的時候,遍歷整棵刪除子樹時需要清空所有標記,但是我忘記清空翻轉和make-same標記了。)

1

//it is made by ljh2000

2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 8 #include 9 #include 10 #include 11 #include 12 #include

13using

namespace

std;

14 typedef long

long

ll;15

const

int inf = (1

<<30

);16

const

int maxn = 500011;17

intrt,tot,n,m;

18int

a[maxn],lx[maxn],rx[maxn],mx[maxn];

19int tr[maxn][2

],same[maxn],tag[maxn],father[maxn];

20int

sum[maxn],size[maxn],val[maxn],id[maxn];

21char ch[12

];22 queueq;

2324 inline int

getint()

2530

31 inline void del(int

x)40

41 inline void update(int

x)45

46 inline void pushdown(int

x)54

if(r) 58}

59if

(tag[x]) 63}

6465 inline void rotate(int x,int &rt)

7374 inline void splay(int x,int &rt)

81rotate(x,rt);82}

83}8485 inline void build(int l,int r,int

fa)91

else build(l,mid-1,mid),build(mid+1

,r,mid);

92 tr[id[fa]][mid>fa]=id[mid]; val[id[mid]]=a[mid];

93 father[id[mid]]=id[fa]; update(id[mid]);94}

9596 inline int rank(int x,int

k)101

102 inline void split(int pos,int num)

106107 inline void

insert()

114int x=rank(rt,pos+1); splay(x,rt); //

!!!115 x=rank(rt,pos+2); splay(x,tr[rt][1

]);116 build(1,num,0); int now_root=id[(1+num)/2

];117 father[now_root]=tr[rt][1]; tr[tr[rt][1]][0]=now_root;

118 update(tr[rt][1

]); update(rt);

119}

120121 inline void

del()

126127 inline void

reverse()

134135 inline void

get_sum()

139140 inline void

makesame()

146147 inline void

work()

160}

161162

intmain()

163

BZOJ1500 NOI2005 維修數列

description input 輸入檔案的第1行包含兩個數n和m,n表示初始時數列中數的個數,m表示要進行的運算元目。第2行包含n個數字,描述初始時的數列。以下m行,每行一條命令,格式參見問題描述中的 output 對於輸入資料中的get sum和max sum操作,向輸出檔案依次列印結果,每個...

bzoj1500 NOI2005 維修數列

splay鼻祖級的題目?霧。insert 把第pos個數 有哨兵節點 轉到root,把第pos 1個數轉到root的右兒子,然後對c建樹然後把這棵樹插到root右兒子的左兒子處 delete 把第pos個數轉到root,把第pos tot 1個數轉到root右兒子,刪掉root右兒子的左兒子 變成0...

BZOJ 1500 NOI2005 維修數列

輸入的第1 行包含兩個數n 和m m 20 000 n 表示初始時數列中數的個數,m表示要進行的運算元目。第2行包含n個數字,描述初始時的數列。以下m行,每行一條命令,格式參見問題描述中的 任何時刻數列中最多含有500 000個數,數列中任何乙個數字均在 1 000,1 000 內。插入的數字總數不...