【暖*墟】 #資料結構# 分塊入門訓練題1~9
阿新 • • 發佈:2018-12-16
引入foreword
數列分塊就是把數列中【每m個元素打包起來】,達到優化演算法的目的。
把每m個元素分為一塊,共有n/m塊,區間修改涉及O(n/m)個整塊,以及兩側兩個不完整的塊。
每次操作對每個整塊直接標記,而由於不完整的塊的元素比較少,暴力修改元素的值。
每次詢問時返回元素的值加上其所在塊的加法標記。每次操作的複雜度是O(n/m)+O(m)。
根據均值不等式,當m取√n時總複雜度最低,所以預設分塊大小為√n,複雜度為O(√n)。
一. 分塊的常用陣列
int n,m; //總個數n,每塊的大小m int a[500019],tag[500019]; //原陣列 和 標記陣列(對於每一塊) int pos[500019]; //pos[i]=(i-1)/m+1,即記錄i屬於哪一塊
還有一些在特定情況下使用的陣列:
vector<int> v[519]; //記錄每塊的元素,並分別排序
//在每塊中維護單調性,用lowerbound函式維護塊的滿足條件的值的個數
二. 分塊的常用函式
【練習1】區間修改,單點查詢
- 每塊標記tag,剩下的l、r兩個邊界塊直接修改。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<string> #include<queue> #include<vector> #include<cmath> #include<map> using namespace std; typedef long long ll; /*【分塊入門練習1】區間修改,單點查詢。*/ /*【分析】數列分塊就是把數列中【每m個元素打包起來】,達到優化演算法的目的。 如果把每m個元素分為一塊,共有n/m塊,區間加會涉及O(n/m)個整塊,以及兩側兩個不完整的塊。 每次操作對每個整塊直接標記,而由於不完整的塊的元素比較少,暴力修改元素的值。 每次詢問時返回元素的值加上其所在塊的加法標記。每次操作的複雜度是O(n/m)+O(m)。 根據均值不等式,當m取√n時總複雜度最低,所以預設分塊大小為√n,複雜度為O(√n)。*/ int n,m,pos[500019]; //pos[i]=(i-1)/m+1,即i屬於哪一塊 int a[500019],tag[500019]; //↓↓每塊標記tag,剩下的l、r兩個邊界塊直接修改 void adds(int l,int r,int x){ //l,r同段 或者 先處理l的邊界段 for(int i=l;i<=min(r,pos[l]*m);i++) a[i]+=x; if(pos[l]!=pos[r]) //同理,處理r的邊界段 for(int i=(pos[r]-1)*m+1;i<=r;i++) a[i]+=x; for(int i=pos[l]+1;i<=pos[r]-1;i++) tag[i]+=x; } int main(/*hs_love_wjy*/){ scanf("%d",&n); m=sqrt(n); for(int i=1;i<=n;i++) pos[i]=(i-1)/m+1; for(int i=1;i<=n;i++) scanf("%d",&a[i]); for(int i=1,op,l,r,x;i<=n;i++){ scanf("%d%d%d%d",&op,&l,&r,&x); if(op==0) adds(l,r,x); if(op==1) printf("%d\n",tag[pos[r]]+a[r]); } //↑↑op=1時,l、x輸入但忽略 }
【練習2】區間修改,查詢比x小的個數
- vector陣列記錄每塊的元素,排序維護塊內的遞增性。
- 完整的塊可以直接用lower_bound返回第一個大於等於x的位置。
- 不完整的塊直接暴力修改,但需要在每塊內每次重新排序。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<string> #include<queue> #include<vector> #include<cmath> #include<map> using namespace std; typedef long long ll; /*【分塊入門練習2】區間修改,查詢比x小的個數。*/ /*【分析】記錄每塊的元素,排序維護塊內的遞增性。 完整的塊可以直接用lower_bound返回第一個大於等於x的位置。 不完整的塊直接暴力修改,但需要在每塊內每次重新排序。*/ int n,m,pos[500019]; //pos[i]=(i-1)/m+1,即i屬於哪一塊 int a[500019],tag[500019]; //↑↑對整塊使用tag標記,在詢問的時候再計算tag的影響 vector<int> v[519]; //記錄每塊的元素,並分別排序 void changes(int num){ v[num].clear(); //↓↓如果是最後一塊,可能不完整 for(int i=(num-1)*m+1;i<=min(num*m,n);i++) v[num].push_back(a[i]); sort(v[num].begin(),v[num].end()); } //↓↓每塊標記tag,剩下的l、r兩個邊界塊直接修改 void adds(int l,int r,int x){ //l,r同段 或者 先處理l的邊界段 for(int i=l;i<=min(r,pos[l]*m);i++) a[i]+=x; changes(pos[l]); //將l塊中的元素重新排序 if(pos[l]!=pos[r]){ //同理,處理r的邊界段 for(int i=(pos[r]-1)*m+1;i<=r;i++) a[i]+=x; changes(pos[r]); //將r塊中的元素重新排序 } for(int i=pos[l]+1;i<=pos[r]-1;i++) tag[i]+=x; } int querys(int l,int r,int x){ int anss=0; //↓↓l,r同段 或者 先處理l的邊界段 for(int i=l;i<=min(r,pos[l]*m);i++) if(a[i]+tag[pos[l]]<x) anss++; //直接列舉統計 if(pos[l]!=pos[r]) //同理,處理r的邊界段 for(int i=(pos[r]-1)*m+1;i<=r;i++) if(a[i]+tag[pos[r]]<x) anss++; for(int i=pos[l]+1;i<=pos[r]-1;i++) anss+=lower_bound(v[i].begin(),v[i].end(),x-tag[i])-v[i].begin(); return anss; //↑↑lower_bound返回第一個大於等於x的位置,省略了位置+1和答案-1 } int main(/*hs_love_wjy*/){ scanf("%d",&n); m=sqrt(n); for(int i=1;i<=n;i++)scanf("%d",&a[i]); for(int i=1;i<=n;i++){ pos[i]=(i-1)/m+1; v[pos[i]].push_back(a[i]); } for(int i=1;i<=pos[n];i++) sort(v[i].begin(),v[i].end()); for(int i=1,op,l,r,x;i<=n;i++){ scanf("%d%d%d%d",&op,&l,&r,&x); if(op==0) adds(l,r,x); //題意中是查詢比x^2小的個數 if(op==1) printf("%d\n",querys(l,r,x*x)); } }