分塊&莫隊
阿新 • • 發佈:2022-05-13
分塊
是一種暴力結構
給定一個序列a,q個詢問,求區間[L,R]權值和。
顯然樹狀陣列,線段樹等結構都可做
沒有學習它們的時候我們是如何解決這個問題的?
字首和
那麼再加上更新
如果不借助上述資料結構只能暴力維護
for(int i=l;i<=r;++i)a[i]+=data;
for(int i=l;i<=r;++i)ans+=a[i]
之所以成分塊是暴力資料結構正是因為分塊就是建立在暴力的基礎之上的。
分塊的核心思想是大段維護,小段暴力
把長度為n的序列分成塊長(block)為sqrt(n)的若干塊(cnt=n/block)。每一個快都有一個邊界l,r,通過數學歸納法可以得出
for(int i=1;i<=cnt;++i){
p[i].l=(i-1)*block+1;
p[i].r=i*block;
}
但是由於分塊題經常卡常,用加法代替相對低效的乘法,通常寫成這種形式
for(int i=1;i<=cnt;++i){
p[i].l=p[i-1].r+1;
p[i].r=p[i].l+block-1;
}
塊裡可以維護很多東西,如果要維護的元素不能實現區間的直接轉移(也就是傳統資料結構不能解決),比如說a在區間中出現的次數,那麼分塊就是較好的選擇
這裡維護塊中所有元素的和,以及一個lazytag(借鑑線段樹的思想,用到的時候再下放)。
更新操作
int bl=belong[l],br=belong[r]; if(bl==br){//在一個塊內暴力 for(int i=l;i<=r;++i){ a[i]+=data; } } else{ for(int i=l;i<=p[bl].r;++i){//不是整塊暴力 a[i]+=data; } for(int i=p[br].l;i<=r;++i){ a[i]+=data; } for(int i=bl+1;i<=br-1;++i){ p[i].add+=data;//懶標記 } }
查詢操作
int bl=belong[l],br=belong[r];
if(bl==br){//在一個塊內暴力
for(int i=l;i<=r;++i){
sum+=a[i];
}
return sum;
}
else{
for(int i=l;i=p[bl].r;++i){
if(p[bl].add){
sum+=p[bl].add;
}
sum+=a[i];
}
for(int i=p[br].l;i<=r;++i){
if(p[br].add){
sum+=p[br].add;
}
sum+=a[i];
}
for(int i=bl+1;i<=br-1;++i){
sum+=p[i].data+p[i].add;
}
}
時間複雜度(塊數)
分塊能解決的問題:
1.RMQ,區間權值和等
2.維護區間眾數 例題:蒲公英
3.區間中第k小的元素 對每個塊排序,然後二分查詢下標 例題:教主的魔法
4.以及各種東西 例題:彈飛綿羊
分塊題目和傳統資料結構題相似,考慮如何預處理,如何查詢,如何更新(這是句廢話)
可以通過數學證明得到最佳塊長實際上為pow(n,2.0/3),時間複雜度由原來的O(n*sqrt(n)),變為O(n^5/3)