詳解? 分塊 未完

2021-09-16 22:16:32 字數 4432 閱讀 6033

不得不說,分塊真是一種優雅的暴力。畢竟人家就是可以暴力掃+部分統計來做到根號複雜度,還能順手解決區間眾數這種線段樹不好解決的問題~

博主的分塊全是跟著黃學長的部落格學的,所以本文不應該叫詳解,頂多是我對分塊的看法+我做分塊九題的心得體會(畢竟學長已經寫得非常好了……)。

在此還是表達一下對黃學長的敬仰和感謝%%%

(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 ...