学习笔记--分块

  • 前言

分块是一种拓展性比较强的数据结构,对于一些难以合并的区间信息,线段树处理起来比较棘手(如区间众数),但是分块以其灵活的特点能够较快直观地处理

本次笔记主要以hzwer的九道分块练习题与博客为主

hzwer介绍分块的博客:http://hzwer.com/8053.html

hzwer的分块练习题:https://loj.ac/problems/search?keyword=%E5%88%86%E5%9D%97

  • 区间加法&区间求和

应该算最为基础的一种问题模型了,用其他数据结构当然能很快地解决,不过我们用这个让大家知道分块的原理.

首先我们要知道"分块",顾名思义,就是把一些信息分成一块一块来处理,于是对于一个区间上的信息修改或查询,这个区间可能既覆盖了一些整块,左右两边又还有一些多出来的部分,或者区间比较小被一个整块给覆盖。

这就给我们处理的思路,对于被区间覆盖的整块,直接对这个整块的信息进行操作,两边多出来零散的部分呢就直接暴力处理。当然还有种情况就是如果区间被一个整块覆盖,也直接对区间暴力处理。这样的话设序列大小为\(N\),询问次数为\(Q\),块的大小为\(\sqrt N\),时间复杂度就为\(O((N+Q)\sqrt N)\)

代码方面个人认为lyd的更简洁易懂,但实际上与hzwer的本质是相同的

\(lydrainbowcat\)

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <cctype>
#include <cmath>
#include <vector>
#include <map>
#include <queue>
#define ll long long 
#define ri register int 
using namespace std;
const int maxn=50005;
const int inf=0xfffffff;
template <class T>inline void read(T &x){
    x=0;int ne=0;char c;
    while(!isdigit(c=getchar()))ne=c=='-';
    x=c-48;
    while(isdigit(c=getchar()))x=(x<<3)+(x<<1)+c-48;
    x=ne?-x:x;
    return ;
}
int n,size; 
int L[maxn],R[maxn],pos[maxn]; //L,R-每个块的左右端点;pos-元素所在块的编号 
ll sum[maxn],tag[maxn],a[maxn];//sum-块的和 tag块的标记 a-元素(序列) 注意开long long  
inline void add(int l,int r,int c){
    int p=pos[l],q=pos[r]; 
    if(p==q){
        for(ri i=l;i<=r;i++){
            a[i]+=c;
        }
        sum[p]+=(r-l+1)*c;
    }
    else{
        for(ri i=p+1;i<=q-1;i++){
            tag[i]+=c;
        }
        for(ri i=l;i<=R[p];i++)a[i]+=c;
        sum[p]+=(R[p]-l+1)*c;
        for(ri i=L[q];i<=r;i++)a[i]+=c;
        sum[q]+=(r-L[q]+1)*c;
    }
}
inline ll query(int l,int r){
    int p=pos[l],q=pos[r];
    ll ans=0;
    if(p==q){
        for(ri i=l;i<=r;i++)ans+=a[i];
        ans+=tag[p]*(r-l+1);
    }
    else{
        for(ri i=p+1;i<=q-1;i++)ans+=sum[i]+tag[i]*(R[i]-L[i]+1);
        for(ri i=l;i<=R[p];i++)ans+=a[i]+tag[p];
        for(ri i=L[q];i<=r;i++)ans+=a[i]+tag[q];
    }
    return ans;
}
int main(){
    read(n);size=sqrt(n);
    for(ri i=1;i<=n;i++){
        read(a[i]);
    }
    for(ri i=1;i<=size;i++){
        L[i]=(i-1)*size+1;   //标记每个块的左右端点位置 
        R[i]=i*size;
    }
    if(R[size]<n){size++;L[size]=R[size-1]+1,R[size]=n;}//如果没凑齐就加一个块 
    for(ri i=1;i<=size;i++){
        for(ri j=L[i];j<=R[i];j++){
            pos[j]=i;
            sum[i]+=a[j];
        }
    }
    int op,l,r,c;
    for(ri i=1;i<=n;i++){
        read(op),read(l),read(r),read(c);
        if(!op){
            add(l,r,c);
        }
        else printf("%d\n",query(l,r)%(c+1));
    }
    return 0;
}

\(hzwer\)

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <cctype>
#include <cmath>
#include <vector>
#include <map>
#include <queue>
#define ll long long 
#define ri register int 
using namespace std;
const int maxn=50005;
const int inf=0xfffffff;
template <class T>inline void read(T &x){
    x=0;int ne=0;char c;
    while(!isdigit(c=getchar()))ne=c=='-';
    x=c-48;
    while(isdigit(c=getchar()))x=(x<<3)+(x<<1)+c-48;
    x=ne?-x:x;
    return ;
}
int n; 
int blo[maxn],block;
ll sum[maxn],a[maxn],tag[maxn];//类似定义 
inline void add(int l,int r,int c){
    for(ri i=l;i<=min(blo[l]*block,r);i++){//处理最左边的块,blo[l]*block其实就是l块的右端点 
        a[i]+=c;
        sum[blo[i]]+=c;
    }
    if(blo[l]!=blo[r]){//如果 左右端点不在同一个块上 
        for(ri i=(blo[r]-1)*block+1;i<=min(r,n);i++){//处理最右块,(blo[r]-1)*block+1其实就是r块的左端点 
            a[i]+=c;
            sum[blo[i]]+=c;
        }
    }
    for(ri i=blo[l]+1;i<=blo[r]-1;i++){//处理整块 
        sum[i]+=block*c;
        tag[i]+=c;
    }
}
inline ll query(int l,int r,int p){
    ll ans=0;
    for(ri i=l;i<=min(blo[l]*block,r);i++){
        ans+=a[i]+tag[blo[i]];
    }
    if(blo[l]!=blo[r]){
        for(ri i=(blo[r]-1)*block+1;i<=min(r,n);i++){
            ans+=a[i]+tag[blo[i]];
        }
    }
    for(ri i=blo[l]+1;i<=blo[r]-1;i++){
        ans+=sum[i];
    }
    return ans;
}
int main(){
    read(n);
    block=sqrt(n);
    for(ri i=1;i<=n;i++){
        read(a[i]);
        blo[i]=(i-1)/block+1;
        sum[blo[i]]+=a[i];
    }
    int op,l,r,c;
    for(ri i=1;i<=n;i++){
        read(op),read(l),read(r),read(c);
        if(!op)add(l,r,c);
        else printf("%d\n",query(l,r,c+1)%(c+1));
    }
    return 0;
}
  • 区间加法&区间小于某数个数

这个思路比较有意思,我们在整块中二分,这就要求整块是有序的,但左右两边散块一但暴力求改后所处的块可能就会不有序,所以要重构那两个块。用vector可以大大减少代码量,但是千万要注意tag即标记对你操作的影响,对于初学者来说非常容易出错

比较有趣的是暴力好象比分块更快

#pragma GCC optimize(3) 
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <cctype>
#include <vector>
#include <map>
#include <queue>
#define ri register int 
#define ll long long 
using namespace std;
const int inf=0xfffffff;
const int maxn=50005;
int a[maxn],blo[maxn],tag[maxn],block,c;
vector <int> g[maxn];
int n;
template <class T>inline void read(T &x){
    x=0;int ne=0;char c;
    while(!isdigit(c=getchar()))ne=c=='-';
    x=c-48;
    while(isdigit(c=getchar()))x=(x<<3)+(x<<1)+c-48;
    x=ne?-x:x;
    return ;
}
inline void reset_block(int now){//重构
    g[now].clear();
    for(ri i=(now-1)*block+1;i<=min(now*block,n);i++){
        g[now].push_back(a[i]);
    }
    sort(g[now].begin(),g[now].end());
}
inline void add(int l,int r){
    for(ri i=l;i<=min(blo[l]*block,r);i++){
        a[i]+=c;
    }
    reset_block(blo[l]);
    if(blo[l]!=blo[r]){
        for(ri i=(blo[r]-1)*block+1;i<=r;i++){
            a[i]+=c;
        }
        reset_block(blo[r]);
    }
    for(ri i=blo[l]+1;i<=blo[r]-1;i++){
        tag[i]+=c;
    }
}
inline int query(int l,int r,int x){
    int cnt=0;
    for(ri i=l;i<=min(blo[l]*block,r);i++){
        if(a[i]+tag[blo[i]]<x)cnt++;
    }
    if(blo[l]!=blo[r]){
        for(ri i=(blo[r]-1)*block+1;i<=min(r,n);i++){
            if(a[i]+tag[blo[i]]<x)cnt++;
        }
    }
    int tmp;
    for(ri i=blo[l]+1;i<=blo[r]-1;i++){
        tmp=lower_bound(g[i].begin(),g[i].end(),x-tag[i])-g[i].begin();
        cnt+=tmp;
    }
    return cnt;
}
int main(){
    read(n);
    block=sqrt(n);
    for(ri i=1;i<=n;i++){
        read(a[i]);
        blo[i]=(i-1)/block+1;
        g[blo[i]].push_back(a[i]);
    }
    for(ri i=1;i<=blo[n];i++){
        sort(g[i].begin(),g[i].end());
    }
    int op,l,r;
    for(ri i=1;i<=n;i++){
        read(op),read(l),read(r),read(c);
        if(!op){
            add(l,r);
        }
        else{
            printf("%d\n",query(l,r,c*c));
        }
    }
    return 0;
}
  • 区间加法&区间前驱

\(X\)的前驱就是小于\(X\)的最大数,用上面那题类似的思路,整块中二分,左右散块修改后重构,查询时暴力查询

然而hzwer大佬用了set,可是我已经对set的常数产生了心理阴影(相对其他STL),同时LOJ讨论区中有人说set可以被hack,感兴趣的可以看一看set写法,这里给出的还是vector二分解法,同时注意tag对操作的影响

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <cctype>
#include <cmath>
#include <vector>
#include <map>
#include <queue>
#define ri register int 
#define ll long long 
using namespace std;
const int inf=0xfffffff;
const int maxn=100005;
template <class T>inline void read(T &x){
    x=0;int ne=0;char c;
    while(!isdigit(c=getchar()))ne=c=='-';
    x=c-48;
    while(isdigit(c=getchar()))x=(x<<3)+(x<<1)+c-48;
    x=ne?-x:x;
    return ;
}
int n;
int blo[maxn],block,tag[maxn],a[maxn];
vector <int>g[maxn];
inline void reset_block(int now){
    g[now].clear();
    for(ri i=(now-1)*block+1;i<=min(now*block,n);i++){
        g[now].push_back(a[i]);
    }
    sort(g[now].begin(),g[now].end());
}
inline void add(int l,int r,int c){
    for(ri i=l;i<=min(blo[l]*block,r);i++){
        a[i]+=c;
    }
    reset_block(blo[l]);
    if(blo[l]!=blo[r]){
        for(ri i=(blo[r]-1)*block+1;i<=min(r,n);i++){
            a[i]+=c;
        }
        reset_block(blo[r]);
    }
    for(ri i=blo[l]+1;i<=blo[r]-1;i++){
        tag[i]+=c;
    }
}
inline int query(int l,int r,int x){
    int ans=-inf;
    for(ri i=l;i<=min(blo[l]*block,r);i++){
        if(a[i]+tag[blo[i]]<x)ans=max(a[i]+tag[blo[i]],ans);
    }
    if(blo[l]!=blo[r]){
        for(ri i=(blo[r]-1)*block+1;i<=min(r,n);i++){
            if(a[i]+tag[blo[i]]<x)ans=max(a[i]+tag[blo[i]],ans);
        }
    }
    int tmp=0;
    for(ri i=blo[l]+1;i<=blo[r]-1;i++){
        tmp=lower_bound(g[i].begin(),g[i].end(),x-tag[i])-g[i].begin();
        if(tmp!=0)ans=max(ans,g[i][tmp-1]+tag[i]);//注意这里要加上标记 
    }
    if(ans==-inf)ans=-1;
    return ans;
}
int main(){
    read(n);
    block=sqrt(n);
    for(ri i=1;i<=n;i++){
        read(a[i]);
        blo[i]=(i-1)/block+1;
        g[blo[i]].push_back(a[i]);
    }
    for(ri i=1;i<=blo[n];i++){
        sort(g[i].begin(),g[i].end());
    }
    int op,l,r,c;
    for(ri i=1;i<=n;i++){
        read(op),read(l),read(r),read(c);
        if(!op){
            add(l,r,c);
        }
        else{
            printf("%d\n",query(l,r,c));
        }
    }
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/Rye-Catcher/p/9194001.html
今日推荐