【分块入门】分块

summary

前面几个都是用来理解分块的思想和练习 能用线段树还是用线段树叭 像lch说的先考虑各种数据结构的优势 尽量用最简单的最适合的

分块一 区间加法 单点查询

给出一个长为n的数列,以及n个操作,操作涉及区间加法,单点查值

给每个块设置一个加法标记,每次操作对每个整块直接O(1)标记,而不完整的块由于元素比较少,暴力修改元素的值。

每次询问时返回元素的值加上其所在块的加法标记

这样每次操作的复杂度是O(n/m)+O(m),根据均值不等式,当m取√n时总复杂度最低

可以画一画区间更直观QAQ

#include<bits/stdc++.h>
using namespace std;
#define Max(x,y) (x)>(y)?(x):(y)
#define Min(x,y) (x)<(y)?(x):(y)
const int N=5e4+5inf=0x3f3f3f3f;
int n,base,a[N],bl[N],addtg[N];
template <class t>void rd(t &x){
    x=0;int w=0;char ch=0;
    while(!isdigit(ch)) w|=ch=='-',ch=getchar();
    while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    x=w?-x:x;
}

void add(int l,int r,int c){
    for(int i=l;i<=(Min(r,bl[l]*base));++i) a[i]+=c;
    if(bl[l]!=bl[r])
    for(int i=(bl[r]-1)*base+1;i<=r;++i) a[i]+=c;
    for(int i=bl[l]+1;i<=bl[r]-1;++i) addtg[i]+=c;
}

int main(){
//    freopen("in.txt","r",stdin);
    rd(n),base=sqrt(n);
    for(int i=1;i<=n;++i) rd(a[i]);
    for(int i=1;i<=n;++i) bl[i]=(i-1)/base+1;
    for(int i=1,op,l,r,c;i<=n;++i){
        rd(op),rd(l),rd(r),rd(c);
        if(!op) add(l,r,c);
        else printf("%d
",a[r]+addtg[bl[r]]);
    }
    return 0;
}

分块二 区间加法 询问区间内小于某数的元素个数

给出一个长为n的数列,以及n个操作,操作涉及区间加法,询问区间内小于某个值x的元素个数。

对于每次区间操作:

1.不完整的块 的O(√n)个元素怎么处理?

2.O(√n)个 整块 怎么处理?

3.要预处理什么信息(复杂度不能超过后面的操作)?

先来思考只有询问操作的情况,不完整的块枚举统计即可;而要在每个整块内寻找小于一个值的元素数,于是我们不得不要求块内元素是有序的,这样就能使用二分法对块内查询,需要预处理时每块做一遍排序,复杂度O(nlogn),每次查询在√n个块内二分,以及暴力2√n个元素,总复杂度O(nlogn + n√nlog√n)。

#include<bits/stdc++.h>
using namespace std;
#define Max(x,y) (x)>(y)?(x):(y)
#define Min(x,y) (x)<(y)?(x):(y)
const int N=5e4+5,M=500+5,inf=0x3f3f3f3f;
int n,base,a[N],bl[N],addtg[N];
vector<int>vec[M];
template <class t>void rd(t &x){
    x=0;int w=0;char ch=0;
    while(!isdigit(ch)) w|=ch=='-',ch=getchar();
    while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    x=w?-x:x;
}

void reset(int x){
    vec[x].clear();
    for(int i=(x-1)*base+1;i<=(Min(x*base,n));++i)
    vec[x].push_back(a[i]);
    sort(vec[x].begin(),vec[x].end());
}
void add(int l,int r,int k){
    for(int i=l;i<=(Min(r,bl[l]*base));++i) a[i]+=k;
    reset(bl[l]);
    if(bl[l]!=bl[r]){
        for(int i=(bl[r]-1)*base+1;i<=r;++i) a[i]+=k;
        reset(bl[r]);
    }
    for(int i=bl[l]+1;i<=bl[r]-1;++i) addtg[i]+=k;
}

int query(int l,int r,int k){
    int ans=0;
    for(int i=l;i<=(Min(r,bl[l]*base));++i)
    if(a[i]+addtg[bl[i]]<k) ++ans;
    if(bl[l]!=bl[r])
    for(int i=(bl[r]-1)*base+1;i<=r;++i)
    if(a[i]+addtg[bl[i]]<k) ++ans;
    for(int i=bl[l]+1,x;i<=bl[r]-1;++i){
        x=k-addtg[i];
        ans+=lower_bound(vec[i].begin(),vec[i].end(),x)-vec[i].begin();
    }
    return ans;
}

int main(){
    freopen("in.txt","r",stdin);
    rd(n),base=sqrt(n);
    for(int i=1;i<=n;++i)
    rd(a[i]),bl[i]=(i-1)/base+1,vec[bl[i]].push_back(a[i]);
    for(int i=1;i<=bl[n];++i)
    sort(vec[i].begin(),vec[i].end());
    for(int i=1,op,l,r,k;i<=n;++i){
        rd(op),rd(l),rd(r),rd(k);
        if(!op) add(l,r,k);
        else printf("%d
",query(l,r,k*k));
    }
    return 0;
}

分块三 区间加法 询问前驱

给出一个长为n的数列,以及n个操作,操作涉及区间加法,询问区间内小于某个值x的前驱(比其小的最大元素

接着第二题的解法,其实只要把块内查询的二分稍作修改即可。

不过这题其实想表达:可以在块内维护其它结构使其更具有拓展性,比如放一个 set ,这样如果还有插入、删除元素的操作,会更加的方便。

分块的调试检测技巧:

可以生成一些大数据,然后用两份分块大小不同的代码来对拍,还可以根据运行时间尝试调整分块大小,减小常数。

本题base=√n比1000慢很多

#include<bits/stdc++.h>
using namespace std;
#define Max(x,y) (x)>(y)?(x):(y)
#define Min(x,y) (x)<(y)?(x):(y)
const int N=1e5+5,M=1000+5,inf=0x3f3f3f3f;
int n,base,a[N],bl[N],addtg[N];
set<int>s[M];
template <class t>void rd(t &x){
    x=0;int w=0;char ch=0;
    while(!isdigit(ch)) w|=ch=='-',ch=getchar();
    while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    x=w?-x:x;
}

void add(int l,int r,int k){
    for(int i=l;i<=(Min(r,bl[l]*base));++i){
        s[bl[i]].erase(a[i]);
        a[i]+=k;
        s[bl[i]].insert(a[i]);
    }
    if(bl[l]!=bl[r]){
        for(int i=(bl[r]-1)*base+1;i<=r;++i){
            s[bl[i]].erase(a[i]);
            a[i]+=k;
            s[bl[i]].insert(a[i]);
        }
    }
    for(int i=bl[l]+1;i<=bl[r]-1;++i) addtg[i]+=k;
}

int query(int l,int r,int k){
    int ans=-1;
    for(int i=l,x;i<=(Min(r,bl[l]*base));++i){
        x=a[i]+addtg[bl[i]];
        if(x<k) ans=Max(ans,x);
    }
    if(bl[l]!=bl[r])
    for(int i=(bl[r]-1)*base+1,x;i<=r;++i){
        x=a[i]+addtg[bl[i]];
        if(x<k) ans=Max(ans,x);
    }
    for(int i=bl[l]+1,x;i<=bl[r]-1;++i){
        x=k-addtg[i];
        set<int>::iterator iter=s[i].lower_bound(x);
        if(iter==s[i].begin()) continue;
        --iter;
        ans=Max(ans,*iter+addtg[i]);
    }
    return ans;
}

int main(){
    freopen("in.txt","r",stdin);
    rd(n),base=1000;
    for(int i=1;i<=n;++i)
    rd(a[i]),bl[i]=(i-1)/base+1,s[bl[i]].insert(a[i]);
    for(int i=1,op,l,r,k;i<=n;++i){
        rd(op),rd(l),rd(r),rd(k);
        if(!op) add(l,r,k);
        else printf("%d
",query(l,r,k));
    }
    return 0;
}

分块四 区间加&区间求和

给出一个长为n的数列,以及n个操作,操作涉及区间加法,区间求和

而要想快速统计完整块的答案,需要维护每个块的元素和,先要预处理一下。

考虑区间修改操作,不完整的块直接改,顺便更新块的元素和;完整的块类似之前标记的做法,直接根据块的元素和所加的值计算元素和的增量。

#include<bits/stdc++.h>
using namespace std;
#define Max(x,y) (x)>(y)?(x):(y)
#define Min(x,y) (x)<(y)?(x):(y)
#define ll long long
const int N=5e4+5,M=500+5,inf=0x3f3f3f3f,P=9999973;
int n,base,bl[N];
ll a[N],sum[N],addtg[N];
template <class t>void rd(t &x){
    x=0;int w=0;char ch=0;
    while(!isdigit(ch)) w|=ch=='-',ch=getchar();
    while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    x=w?-x:x;
}

void add(int l,int r,ll k){
    for(int i=l;i<=(Min(r,bl[l]*base));++i) a[i]+=k,sum[bl[i]]+=k;
    if(bl[l]!=bl[r])
        for(int i=(bl[r]-1)*base+1;i<=r;++i) a[i]+=k,sum[bl[i]]+=k;
    for(int i=bl[l]+1;i<=bl[r]-1;++i) addtg[i]+=k;
}

ll query(int l,int r,ll k){
    ll ans=0;
    for(int i=l;i<=(Min(r,bl[l]*base));++i) ans=(ans+a[i]+addtg[bl[i]])%k;
    if(bl[l]!=bl[r])
    for(int i=(bl[r]-1)*base+1;i<=r;++i) ans=(ans+a[i]+addtg[bl[i]])%k;
    for(int i=bl[l]+1;i<=bl[r]-1;++i)
    ans=(ans+sum[i]+addtg[i]*base)%k;
    return ans;
}

int main(){
    freopen("in.txt","r",stdin);
    rd(n),base=sqrt(n);
    for(int i=1;i<=n;++i)
    rd(a[i]),bl[i]=(i-1)/base+1,sum[bl[i]]+=a[i];
    for(int i=1,op,l,r,k;i<=n;++i){
        rd(op),rd(l),rd(r),rd(k);
        if(!op) add(l,r,k);
        else printf("%lld
",query(l,r,k+1)%(k+1));
    }
    return 0;
}

分块五 区间开方&区间求和

和线段树做这题的思想差不多 也要维护一个当前区间是否全部为0/1

#include<bits/stdc++.h>
using namespace std;
#define Max(x,y) (x)>(y)?(x):(y)
#define Min(x,y) (x)<(y)?(x):(y)
#define ll long long
const int N=5e4+5,M=500+5,inf=0x3f3f3f3f,P=9999973;
int n,base,bl[N];
ll a[N],sum[N],addtg[N],tg[N];
template <class t>void rd(t &x){
    x=0;int w=0;char ch=0;
    while(!isdigit(ch)) w|=ch=='-',ch=getchar();
    while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    x=w?-x:x;
}

void updat(int x){
    if(tg[x]) return;
    tg[x]=1,sum[x]=0;
    for(int i=(x-1)*base+1;i<=x*base;++i){
        a[i]=sqrt(a[i]),sum[x]+=a[i];
        if(a[i]>1) tg[x]=0;
    }
}
void sqr(int l,int r){
    for(int i=l;i<=(Min(r,bl[l]*base));++i){
        sum[bl[i]]-=a[i];
        a[i]=sqrt(a[i]);
        sum[bl[i]]+=a[i];
    }
    if(bl[l]!=bl[r])
    for(int i=(bl[r]-1)*base+1;i<=r;++i){
        sum[bl[i]]-=a[i];
        a[i]=sqrt(a[i]);
        sum[bl[i]]+=a[i];
    }
    for(int i=bl[l]+1;i<=bl[r]-1;++i) updat(i);
}

ll query(int l,int r){
    ll ans=0;
    for(int i=l;i<=(Min(r,bl[l]*base));++i) ans+=a[i];
    if(bl[l]!=bl[r])
    for(int i=(bl[r]-1)*base+1;i<=r;++i) ans+=a[i];
    for(int i=bl[l]+1;i<=bl[r]-1;++i) ans+=sum[i];
    return ans;
}

int main(){
    freopen("in.txt","r",stdin);
    rd(n),base=sqrt(n);
    for(int i=1;i<=n;++i)
    rd(a[i]),bl[i]=(i-1)/base+1,sum[bl[i]]+=a[i];
    for(int i=1,op,l,r,k;i<=n;++i){
        rd(op),rd(l),rd(r),rd(k);
        if(!op) sqr(l,r);
        else printf("%lld
",query(l,r));
    }
    return 0;
}

分块六 单点插入&单点查询

给出一个长为n的数列,以及n个操作,操作涉及单点插入,单点询问,数据随机生成。

此题每块内可以放一个动态的数组,每次插入时先找到位置所在的块,再暴力插入,把块内的其它元素直接向后移动一位,当然用链表也是可以的。

如果先在一个块有大量单点插入,这个块的大小会大大超过√n,那块内的暴力就没有复杂度保证了 还需要引入一个操作:重新分块(重构)

每根号n次插入后,重新把数列平均分一下块,重构需要的复杂度为O(n),重构的次数为√n,所以重构的复杂度没有问题,而且保证了每个块的大小相对均衡。当然,也可以当某个块过大时重构,或者只把这个块分成两半。

并未完全理解代码 所以先折叠

#include<bits/stdc++.h>
using namespace std;
#define Max(x,y) (x)>(y)?(x):(y)
#define Min(x,y) (x)<(y)?(x):(y)
#define ll long long
#define Abs(x) (x)<0?-x:x;
#define rg register
const int N=1e5+5,M=1000+5,inf=0x3f3f3f3f,P=9999973;
int n,m,base,a[N],st[N],top;
vector<int>v[M];
template <class t>void rd(t &x){
    x=0;int w=0;char ch=0;
    while(!isdigit(ch)) w|=ch=='-',ch=getchar();
    while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    x=w?-x:x;
}

pair<int,int>query(int pos){
    int x=1;
    while(pos>v[x].size()) pos-=v[x].size(),++x;
    return make_pair(x,pos-1);
}

void rebuild(){
    top=0;
    for(int i=1;i<=m;++i){
        for(vector<int>::iterator j=v[i].begin();j!=v[i].end();++j) st[++top]=*j;
        v[i].clear();
    }
    int base2=sqrt(top);
    for(int i=1;i<=top;++i) v[(i-1)/base2+1].push_back(st[i]);
    m=(top-1)/base2+1;
}

void insert(int pos,int x){
    pair<int,int> t=query(pos);
    v[t.first].insert(v[t.first].begin()+t.second,x);
    if(v[t.first].size()>base*20) rebuild();
}


int main(){
    freopen("in.txt","r",stdin);
    rd(n),base=sqrt(n),m=(n-1)/base+1;
    for(int i=1;i<=n;++i) rd(a[i]),v[(i-1)/base+1].push_back(a[i]);
    for(int i=1,op,l,r,k;i<=n;++i){
        rd(op),rd(l),rd(r),rd(k);
        if(!op) insert(l,r);
        else{
            pair<int,int> t=query(r);
            printf("%d
",v[t.first][t.second]);
        }
    }
    return 0;
}
View Code

 

原文地址:https://www.cnblogs.com/lxyyyy/p/11271333.html