线性结构 —— 分块算法
【概述】
所謂分塊,即對于一個長度為 n 的序列,設塊的大小為 S,從序列的第一個元素起,每 S 個元素被分成一塊,若剩余的元素不足 S 個,令他們組成一塊。經過分塊后的數組,稱為塊狀數組,在塊狀數組的基礎上加以擴展,即可得到塊狀鏈表。
在一個區間操作時,完整包含于區間的塊稱為整塊,只有部分包含于區間的塊,稱為不完整的塊,不完整的塊實質上行即為區間左右端點所在的兩個塊。
在許多區間問題中,使用樹型結構,如:樹狀數組、線段樹等會更加優秀,但當所需的某些信息無法快速合并時,就只能使用分塊,例如:詢問一個區間的眾數
需要注意的是,利用來解決的問題,大部分都是要求強制在線的,此外,在時間復雜度上:樹狀數組?> 線段樹 > 塊狀數組(分塊)
但三者各有優劣:
- 樹狀數組:代碼簡單、常數小
- 線段樹:代碼復雜、功能強大、常數大
- 塊狀數組:代碼復雜、常數大、能維護一些難以快速合并的數據
【基本原理】
從時間復雜度來考慮,一般來說,分塊長度為 sqrt(n),那么,初始化時有:
int block,sum;//block為塊的長度,sum為塊的個數 int a[N];//存放數列元素 int pos[N],tag[N];//pos記錄第i個元素在第幾個塊中,tag為操作標記 int ans[N];//維護整塊和 void init(){//初始化block=(int)sqrt(n) //塊的長度sum=n/block;//塊數if(n%block)sum++;for(int i=1;i<=n;i++)//第i個元素在第幾塊中pos[i]=(i-1)/block+1; }對于區間查詢,有三種情況:
由于可以預處理每一塊的值,因此對于一塊來說,查詢的時間復雜度是 O(1) 的,對于多出來的邊角料的部分總和不會超過 2*sqrt(n),基于這個時間復雜度,進行暴力求解,可以發現對于第一種情況,q?次查詢的復雜度為 O(q*sqrt(n)),對于第二、三中情況,完全可以當成情況 1 來處理
對于一個塊的左邊界,可以求出上一個塊的右邊界 (pos[x]-1)*block,那么 +1 后就是這個塊的左邊界,即:(pos[x]-1)*block+1
因此,在每次詢問時,第 i 個元素 a[i] 處于第 (i-1)/sqrt(n)+1 塊,其左端點為 (i-1)*sqrt(n)+1,右端點為 min(i*sqrt(n),n),返回元素的值再加上其所在塊的標記即可。
int block,sum;//block為塊的長度,sum為塊的個數 int a[N];//存放數列元素 int pos[N],tag[N];//pos記錄第i個元素在第幾個塊中,tag為操作標記 int ans[N];//維護整塊和 int query(int L,int R){for(int i=L;i<=min(R,pos[L]*block);i++)//處理左邊角料//do somethingfor(int i=(pos[R]-1)*block+1;i<=R;i++)//處理右邊角料//do somethingfor(int i=pos[L]+1;i<=pos[R]-1;i++)//處理中間k塊//do something }對于修改操作,與查詢操作一樣,同樣分成三部分,對于邊角料暴力修改 a[i],對于每一塊,修改每一塊的標記值 tag[pos[i]],那么,對于 m 次修改,總時間復雜度為 O(m*sqrt(n))?
int block,sum;//block為塊的長度,sum為塊的個數 int a[N];//存放數列元素 int pos[N],tag[N];//pos記錄第i個元素在第幾個塊中,tag為操作標記 void change(int x){//do something } void update(int L,int R,int x){for(int i=L;i<=min(R,pos[L]*block);i++)//處理左邊角料,對a[i]進行操作change(x);if(pos[L]!=pos[R])//存在右區間才遍歷,防止重復計算for(int i=(pos[R]-1)*block+1;i<=R;i++)//處理右邊角料,對a[i]進行操作change(x);for(int i=pos[L]+1;i<=pos[R]-1;i++)//處理中間k塊,對tag[i]進行操作change(x); }?而對于整塊的預處理,即使每個塊暴力進行對 ans[i] 的預處理,時間復雜度也只有 O(n)
int ans[N];//維護整塊 void work(int L,int R,int x){int start=0;for(int i=L;i<=R;i++)//do something with startans[x]=start;//整塊的標記 }int mian(){...for(int i=1;i<=sum;i++)//傳入塊的左右邊界與編號work((i-1)*block+1,i*block,i);...}【模版&例題】
模版原理:分塊九講
- 數列分塊入門 1(LibreOj-6277)(區間加法,單點詢問):點擊這里
- 數列分塊入門 2(LibreOj-6278)(區間加法,詢問區間內小于某個值 x 的元素個數):點擊這里
- 數列分塊入門 3(LibreOj-6279)(區間加法,詢問區間內小于某個值 x 的前驅):點擊這里
- 數列分塊入門 4(LibreOj-6280)(區間加法,詢問區間和):點擊這里
- 數列分塊入門 5(LibreOj-6281)(區間開方,詢問區間和):點擊這里
- 數列分塊入門 6(LibreOj-6282)(單點插入,單點詢問):點擊這里
- 數列分塊入門 7(LibreOj-6283)(區間乘法與加法,單點詢問):點擊這里
- 數列分塊入門 8(LibreOj-6284)(詢問區間某值個數,區間賦值):點擊這里
- 數列分塊入門 9(LibreOj-6285)(詢問區間眾數):點擊這里
總結
以上是生活随笔為你收集整理的线性结构 —— 分块算法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 训练日志 2019.1.19
- 下一篇: 理论基础 —— 索引 —— 2-3 树