不得不說,分塊真是一種優雅的暴力。畢竟人家就是可以暴力掃+部分統計來做到根號複雜度,還能順手解決區間眾數這種線段樹不好解決的問題~
博主的分塊全是跟著黃學長的部落格學的,所以本文不應該叫詳解,頂多是我對分塊的看法+我做分塊九題的心得體會(畢竟學長已經寫得非常好了……)。
在此還是表達一下對黃學長的敬仰和感謝%%%
(p.s:為了表現出我沒有咕咕咕所以即使沒有寫完還是發上來了233)
給定n個數,m個操作,操作包括區間/單點加,區間/單點查詢這樣的題我們見得多了,畢竟是線段樹的模板題內容嘛。
但要是不用線段樹呢?
我們來思考一下,線段樹處理這些問題為什麼那麼快?
1、面對單點操作,複雜度為樹高,即每次操作o(l
ogn)
o(logn)
o(logn) (
logn
)o(logn)
o(logn
) 我們如果用陣列來近似處理的話,單點的操作都可以在o(1)的時間內做到,但是區間呢?既然線段樹可以用點對應區間,我們是不是可以考慮把序列分成區間來處理?然後只需要統計一下區間資訊,給區間打標記,是不是也可以快速回答上述操作?
好了,分塊的基本思想就是這樣:把序列劃分為多段區間。如果查詢範圍小,我們可以直接暴力掃;當查詢範圍變大,包含了我們統計過的區間,我們對於零散的部分暴力統計,而被包含在查詢範圍內的區間,則可以用已經統計過的資訊來進行回答了。
當然隨便劃分是不行的。經過眾位神仙的推導,得出當塊的大小為n
\sqrt n
n時,均攤複雜度可以達到最小值。(當然某些毒瘤題的分塊大小也可能是n
3\sqrt[3]
3n)
p.s:不知道各位在考場上打暴力的時候有沒有用過類似的思想……反正我曾經用類似的分段統計的思想+打表強行過了矩陣快速冪的題……
一般情況下,我們用陣列存放原序列,用動態陣列來維護每個塊(其實很多時候不需要)。當然這還是因題而異。比如你可以用鍊錶存放原序列,用平衡樹維護每個塊。
① 塊的劃分
首先是一開局就要做的事:進行塊的劃分和資訊的統計。
我們用v[i
]v[i]
v[i]
存放原序列,用bl[
i]bl[i]
bl[i
]表示原序列中第i
ii位屬於第b[i
]b[i]
b[i]
個塊,tot
[i]tot[i]
tot[i]
存每塊的和。
s qsq
sq是分塊大小,n
nn是序列長度:
const
int n=
1e6+5;
int n,v[n]
,bl[n]
,sq;
intmain()
}
②基本操作:區間查詢、區間加
(單點查值&加我就不講了,大家都是聰明人)
秉承分塊的思路:整塊靠統計,部分上暴力。
兩邊迴圈統計,中間的直接呼叫tot
[i]tot[i]
tot[i]
即可。1、區間查詢
我們對照著圖來看**。(黑色的是劃分出的塊)(圖醜見諒)
}2、區間加
然後是區間加。在這裡我們延續線段樹的習慣——打標記。
兩邊暴力,中間打標記即可。
**:(其實長得幾乎一樣啊喂)
int tag_a[n]
;//用乙個陣列來記錄第 i 塊的加法標記
ivoid add_some
(int l,
int r,
int x)
}
那麼現在你可以做這
些了。這個區間開方的也可以考慮一下(雖然***要講),跟線段樹的做法近似。
③重構相關:區間乘、區間查詢前驅
將這兩個操作放到這裡說,主要是為了介紹分塊的另乙個重要函式——重構res
etreset
reset。
某些時候我們對塊進行區域性修改之後,會影響資訊的統計,這時候就要把整個塊重構一次,順便下傳標記啊統計資訊啊什麼的。
1、區間乘
首先是區間乘。線段樹上做乘法的時候,我們會把加法標記也做一次乘法處理,分塊同理,所以就該輸出(對應值×
\times
×乘法表記)+加法標記
嗎?並不。
當我們修改塊的部分時,可能會影響到資訊的正確性。舉個例子:
一共九個數,a[4]=4。tag1為乘法標記,tag2為加法標記很明顯,我們處理邊上兩塊的時候容易出問題。那怎麼辦呢?重構吧。操作1:a[2]~a[6] 乘 3
操作2:a[1]~a[4] 加 2
正確答案:a[4]=14
實際情況:
第一次操作中,a[4]所在塊被打上乘3的標記,a[4]=4,tag1=3,tag2=0.
第二次操作中,a[4]+=2,a[4]=6,tag1=3,tag2=0.
輸出結果:cout<< (a[4]*tag1)+tag2 輸出18 wa
每當我們要對塊的部分進行修改,先重構這個塊,把標記全部下傳,再進行部分修改。這樣雖然看起來暴力,但卻是行之有效的方法。(且複雜度不會炸媽)(且我不會證)
**:
//題目有要求取模,我就不刪 %mod 了=
ivoid reset
(int x)
//無論是加還是乘都有reset,別的和線段樹無異……吧?
ivoid addsome
(int l,
int r,
int x)
}ivoid mulsome
(int l,
int r,
int x)
}
2、區間查詢前驅
你看,這個線段樹就處理不了~ (權值線段樹冷笑一聲)
這個問題就很有意思了,畢竟我們的v[i
]v[i]
v[i]
是不能改變順序的,也就意味著我們沒辦法在原陣列上快速統計前驅。
那就沒辦法了嗎??當然有。還記得我說過「用動態陣列維護每個塊」嗎?
那麼具體怎麼做呢??
首先,我們在最開始分塊時,把每個塊的元素全加到對應的vec
vecve
c裡面去:
vector<
int,
int> block[
2005];
//其實sqrt(n)個塊哪需要開這麼大……
for(rint i=
1;i<=n;i++
)//此題不需要統計區間和,所以去掉了tot[i]
然後,我們對每個塊排序。沒錯,直接排序:
for
(rint i=
1;i<=bl[n]
;i++
)sort
(block[i]
.begin()
,block[i]
.end()
);
那麼都是排好序的了,我們查詢自然就方便了:
ivoid query_pre
(int a,
int b,
int c)
//區間查詢x的前驅
if(bl[a]
!=bl[b]
)for
(rint i=bl[a]+1
,t;i<=bl[b]-1
;i++)}
cout<<
(mx==
-inf?-1
:mx)
<
}
emmm……這麼簡單?當然不是。如果我們還要同時維護區間加該怎麼辦呢?(笑
請務必記住:分塊的本質是暴力!暴力!暴力!(優雅的
所以……
正常的區間加+塊內重構就完事了!~(對就這麼簡單,重構就完事了)
ivoid reset
(int x)
//分塊內部の重塑
ivoid addsome
(int a,
int b,
int c)
//區間加
}
如果你確認自己已經懂了,那麼這個,這個和這個都可以做啦~
③高階操作:區間開方、區間查詢某個值的個數&區間覆蓋、單點插值
待更新~~~~~~~(也許要等到下週了23333
分塊演算法詳解
分塊演算法 1.思想 如果我們需要對乙個特定的序列進行操作,那麼非常直觀 簡單的方法就是純暴力 不,那叫模擬 不過如果暴力能過的話,那就呵呵了。所以我們要想一些比較高能的資料結構 分塊。相比線段樹來說,分塊演算法比較難實現,但是只要深入理解,就可以實現了,只不過需要一些資料結構的輔助。分塊實質來說就...
分塊 基本思想詳解
本蒟蒻剛剛看懂了分塊於是就出來寫一篇部落格 千燈給你們整理一下分塊。一般來說,看到給乙個區間 l,r 增加一定值,或者搜尋乙個區間那麼就可以用樹狀陣列 分塊做。樹狀陣列比分塊快 分塊其實是一種優化過的暴力。首先我們真的要把這個東東切塊 一般來說分成n sqrt n n 塊 分不平均沒事極力掩蓋畫圖水...
python yield用法詳解(未完成)
可以把yield看成是return,這個是直觀的,首先是個return,普通的return就是在程式中返回某個值,返回之後程式就不再往下執行了。看做return之後再把它看做是乙個生成器的一部分 def foo print starting.while true res yield 4 print ...