1. 程式人生 > >【暖*墟】 #資料結構# 分塊入門訓練題1~9

【暖*墟】 #資料結構# 分塊入門訓練題1~9

引入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));
    }
}