輸入的第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 內。插入的數字總數不...