分块

分块——优雅的暴力

参考

分块算法的思想是通过适当的划分,预处理一部分信息保存下来,用空间换取时间,达到时空平衡。

分块实现的基本框架:
划分块(一般将其分为\(\sqrt n\)块,每块也有\(\sqrt n\)个元素),预处理,操作或查询。
操作或查询通常为4步:
1.判断要操作或是查询的区间是否在一个块内
2.若在一个块内,暴力操作或查询 \(O(\sqrt n)\)
3.若不在一个块内,将除了最左边和最右边这两个角块外 其余的块 进行整体的操作,即直接对块打上修改标记之类的 \(O(\sqrt n)\)
4.单独暴力处理最左边的块和最右边的角块 \(O(\sqrt n)\)

一些代码含义

b[] —— belong 第\(i\)个数所在块的编号——我们想把\([1,size]\)分在一个块里,所以 是 \((i-1)/size+1\) ;

\(size\) 块大小

对于一个编号为\(k\)的块,其表示的范围为\([(k-1)*size+1,k*size]\)

分块入门 1

区间加法,单点查值

#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
#define N 50005
inline int read(){
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
int n,a[N],tag[N],op,l,r,c;
int size,b[N];
void add(int l,int r,int c) {
   if(b[l]==b[r]) {
       for(int i=l;i<=r;i++)
            a[i]+=c;
   }else {
       for(int i=l;i<=b[l]*size;i++)
            a[i]+=c;
        for(int i=b[l]+1;i<=b[r]-1;i++)
            tag[i]+=c;
        for(int i=(b[r]-1)*size+1;i<=r;i++)
            a[i]+=c;
   }
}
int main() {
    n=read();
    size=sqrt(n);
    for(int i=1;i<=n;i++) 
        a[i]=read(),b[i]=(i-1)/size+1;
    for(int i=1;i<=n;i++) {
        op=read();l=read();r=read();c=read();
        if(op==0) add(l,r,c);
        if(op==1) printf("%d\n",a[r]+tag[b[r]]);
    }
    return 0;
}

分块入门 2

区间加法,询问区间内小于某个值 的元素个数

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

那么区间加呢?

套用第一题的方法,维护一个加法标记,略有区别的地方在于,角块修改后可能会使得该块内数字乱序,所以头尾两个角块需要重新排序

在加法标记下的询问操作,块外还是暴力,查询小于(x – 加法标记)的元素个数,块内用(x – 加法标记)作为二分的值即可。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<vector>
#include<algorithm>
using namespace std;
#define N 500005
inline int read(){
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
int n,a[N],tag[N],op,l,r,c;
int size,b[N];
vector<int>v[5005];
void reset(int x) {
    v[x].clear();
    for(int i=(x-1)*size+1;i<=min(x*size,n);i++)
        v[x].push_back(a[i]);
    sort(v[x].begin(),v[x].end());
}
void add(int l,int r,int c) {
    if(b[l]==b[r]) {
        for(int i=l;i<=r;i++)
            a[i]+=c;
        reset(b[l]);
    } else {
        for(int i=l;i<=b[l]*size;i++)
            a[i]+=c;
        reset(b[l]);
        for(int i=b[l]+1;i<=b[r]-1;i++)
            tag[i]+=c;
        for(int i=(b[r]-1)*size+1;i<=r;i++)
            a[i]+=c;
        reset(b[r]);
    }
}
int query(int l,int r,int c) {
    int ans=0;
    if(b[l]==b[r]) {
        for(int i=l;i<=r;i++)
                if(a[i]+tag[b[i]]<c) ans++;
        return ans;
    } else {
        for(int i=l;i<=b[l]*size;i++)
                if(a[i]+tag[b[i]]<c) ans++;
        for(int i=b[l]+1;i<=b[r]-1;i++) {
            int x=c-tag[i];
            ans+=lower_bound(v[i].begin(),v[i].end(),x)-v[i].begin();
        }
        for(int i=(b[r]-1)*size+1;i<=r;i++)
            if(a[i]+tag[b[i]]<c) ans++;
        return ans;
    }
}
int main() {
    n=read();
    size=sqrt(n);
    for(int i=1;i<=n;i++) {
        a[i]=read();
        b[i]=(i-1)/size+1;
        v[b[i]].push_back(a[i]);
    }
    for(int i=1;i<=b[n];i++)
        sort(v[i].begin(),v[i].end());
    for(int i=1;i<=n;i++) {
        op=read();l=read();r=read();c=read();
        if(op==0) add(l,r,c);
        if(op==1) printf("%d\n",query(l,r,c*c));
    }
    return 0;
}

一道考试题

很类似

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cmath>
#include<vector>
using namespace std;
#define N 1000005
int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
vector<int> v[1005];
int n,size,m;
int a[N],b[N],tag[N];
void reset(int x){
    v[x].clear();
    for(int i=(x-1)*size+1;i<=min(x*size,n);i++)
        v[x].push_back(a[i]);
    sort(v[x].begin(),v[x].end());
}
void add(int l,int r,int c){
    for(int i=l;i<=min(r,b[l]*size);i++)
        a[i]+=c;
    reset(b[l]);
    if(b[l]!=b[r]){
        for(int i=(b[r]-1)*size+1;i<=r;i++)
            a[i]+=c;
        reset(b[r]);
    }
    for(int i=b[l]+1;i<=b[r]-1;i++)
        tag[i]+=c;
}
int query(int l,int r,int c){
    int ans=0;
    for(int i=l;i<=min(b[l]*size,r);i++)
        if(a[i]+tag[b[l]]>=c)ans++;
    if(b[l]!=b[r])
        for(int i=(b[r]-1)*size+1;i<=r;i++)
            if(a[i]+tag[b[r]]>=c)ans++;
    for(int i=b[l]+1;i<=b[r]-1;i++){
        int x=c-tag[i];
        ans+=size-(lower_bound(v[i].begin(),v[i].end(),x)-v[i].begin());
    }
    return ans;
}
int main(){
    n=read();m=read();size=sqrt(n);
    for(int i=1;i<=n;i++)a[i]=read();
    for(int i=1;i<=n;i++){
        b[i]=(i-1)/size+1;
        v[b[i]].push_back(a[i]);
    }
    for(int i=1;i<=b[n];i++)
        sort(v[i].begin(),v[i].end());
    for(int i=1;i<=m;i++){
        char f;cin>>f;
        int x=read(),y=read(),z=read();
        if(f=='M')add(x,y,z);
        if(f=='A')printf("%d\n",query(x,y,z));
    }
    return 0;
}

分块入门 3

区间加法,询问区间内小于某个值x的前驱(比其小的最大元素)。

n<=100000其实是为了区分暴力和一些常数较大的写法

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

#include<set>
#include<cmath>
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
#define N 100005
inline int read(){
	int x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
	return f*x;
}
set<int>s[505];
int size,b[N];
int n,a[N],tag[N],op,l,r,c;
void add(int l,int r,int c) {
    if(b[l]==b[r]) {
        for(int i=l;i<=r;i++)
            s[b[i]].erase(a[i]),a[i]+=c,s[b[i]].insert(a[i]);
    }else {
        for(int i=l;i<=b[l]*size;i++)
            s[b[i]].erase(a[i]),a[i]+=c,s[b[i]].insert(a[i]);
        for(int i=b[l]+1;i<=b[r]-1;i++)
            tag[i]+=c;
        for(int i=(b[r]-1)*size+1;i<=r;i++)
            s[b[i]].erase(a[i]),a[i]+=c,s[b[i]].insert(a[i]);
    }
}

int query(int l,int r,int c) {
    int ans=-1;
    if(b[l]==b[r]) {
        for(int i=l;i<=r;i++)
            if(a[i]+tag[b[i]]<c) 
                ans=max(ans,a[i]+tag[b[i]]);
        return ans;
    }else {
        for(int i=l;i<=b[l]*size;i++)
            if(a[i]+tag[b[i]]<c) 
                ans=max(ans,a[i]+tag[b[i]]);
        for(int i=b[l]+1;i<=b[r]-1;i++) {
            int x=c-tag[i];
            set<int>::iterator it=s[i].lower_bound(x);
            if(it==s[i].begin()) continue;
            --it;
            ans=max(ans,*it+tag[i]);
        }
        for(int i=(b[r]-1)*size+1;i<=r;i++)
            if(a[i]+tag[b[i]]<c)
                ans=max(ans,a[i]+tag[b[i]]);
        return ans;
    }
}
int main() {
    n=read();
    size=1000;
    for(int i=1;i<=n;i++) {
        a[i]=read();
        b[i]=(i-1)/size+1;
        s[b[i]].insert(a[i]);
    }
    for(int i=1;i<=n;i++) {
        op=read();l=read();r=read();c=read();
        if(op==0) add(l,r,c);
        if(op==1) printf("%d\n",query(l,r,c));
    }
    return 0;
}

分块入门 4

区间加法,区间求和

so easy ~ 开long long

#include<cmath>
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
#define int long long
#define N 100005
inline int read(){
	int x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
	return f*x;
}
int size,b[N];
int n,a[N],tag[N],op,l,r,c,sum[N];
bool flag[N];

void add(int l,int r,int c) {
    if(b[l]==b[r]) {
        for(int i=l;i<=r;i++)
            sum[b[i]]+=c,a[i]+=c;
    } else {
        for(int i=l;i<=b[l]*size;i++)
            sum[b[i]]+=c,a[i]+=c;
        for(int i=b[l]+1;i<=b[r]-1;i++)
            tag[i]+=c;
        for(int i=(b[r]-1)*size+1;i<=r;i++)
            sum[b[i]]+=c,a[i]+=c;
    }
}

int query(int l,int r,int c) {
    int ans=0;
    if(b[l]==b[r]) {
        for(int i=l;i<=r;i++)
            ans=(ans+a[i]+tag[b[i]])%c;
        return ans;
    } else {
        for(int i=l;i<=b[l]*size;i++)
            ans=(ans+a[i]+tag[b[i]])%c;
        for(int i=b[l]+1;i<=b[r]-1;i++) {
            ans=(ans+tag[i]*size+sum[i])%c;
        }
        for(int i=(b[r]-1)*size+1;i<=r;i++)
            ans=(ans+a[i]+tag[b[i]])%c;
        return ans;
    }
}
signed main() {
    n=read();
    size=sqrt(n);
    for(int i=1;i<=n;i++) {
        a[i]=read();
        b[i]=(i-1)/size+1;
        sum[b[i]]+=a[i];
    }
    for(int i=1;i<=n;i++) {
        op=read();l=read();r=read();c=read();
        if(op==0) add(l,r,c);
        if(op==1) printf("%lld\n",query(l,r,c+1));
    }
    return 0;
}

分块入门 5

区间开方,区间求和。

稍作思考可以发现,开方操作比较棘手,主要是对于整块开方时,必须要知道每一个元素,才能知道他们开方后的和,也就是说,难以快速对一个块信息进行更新。

不难发现,这题的修改就只有下取整开方,而一个数经过几次开方之后,它的值就会变成 0 或者 1。

对于角块,不超过2√n个元素,直接暴力。

对于整块,这些块经过几次操作以后就会都变成 0 / 1,于是我们记录一下元素是否都变成了 0 / 1,区间修改时跳过那些全为 0 / 1 的块即可。

这样每个元素至多被开方不超过4次,显然复杂度没有问题。

#include<cmath>
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
#define int long long
#define N 100005
inline int read(){
	int x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
	return f*x;
}
int size,b[N];
int n,a[N],op,l,r,c,sum[N];
bool flag[N];

void solve(int x) {
    if(flag[x]) return;
    flag[x]=1;
    sum[x]=0;
    for(int i=(x-1)*size+1;i<=x*size;i++) {
        a[i]=sqrt(a[i]),sum[x]+=a[i];
        if(a[i]>1) flag[x]=0;
    }
}

void SQRT(int l,int r) {
    if(b[l]==b[r]) {
        for(int i=l;i<=r;i++)
            sum[b[i]]-=a[i],a[i]=sqrt(a[i]),sum[b[i]]+=a[i];
    } else {
        for(int i=l;i<=b[l]*size;i++)
            sum[b[i]]-=a[i],a[i]=sqrt(a[i]),sum[b[i]]+=a[i];
        for(int i=b[l]+1;i<=b[r]-1;i++)
            solve(i);
        for(int i=(b[r]-1)*size+1;i<=r;i++)
            sum[b[i]]-=a[i],a[i]=sqrt(a[i]),sum[b[i]]+=a[i];
    }
}

int query(int l,int r) {
    int ans=0;
    if(b[l]==b[r]) {
        for(int i=l;i<=r;i++)
            ans+=a[i];
        return ans;
    } else {
        for(int i=l;i<=b[l]*size;i++)
            ans+=a[i];
        for(int i=b[l]+1;i<=b[r]-1;i++) 
            ans+=sum[i];
        for(int i=(b[r]-1)*size+1;i<=r;i++)
            ans+=a[i];
        return ans;
    }
}
signed main() {
    n=read();
    size=sqrt(n);
    for(int i=1;i<=n;i++) {
        a[i]=read();
        b[i]=(i-1)/size+1;
        sum[b[i]]+=a[i];
    }
    for(int i=1;i<=n;i++) {
        op=read();l=read();r=read();c=read();
        if(op==0) SQRT(l,r);
        if(op==1) printf("%lld\n",query(l,r));
    }
    return 0;
}

Chef and Churu

有一个长度为 n 的数组 A,元素编号 1 - n。还有 n 个函数,函数编号 1 - n,第 i 个函数返回 L[i] 和 R[i] 之间的数的和。 支持两种操作: 将 A[x] 修改为 y。 求编号在 l 到 r 之间函数的返回值的和。 1≤n,m≤10^5

(函数就是一段区间和,然后让求几个函数的和)

Solution

函数可以用树状数组维护,然后所有函数用分块维护

问题在于修改一个地方会影响n个函数,然后再去影响\(\sqrt(n)\) 个块

怎么把影响n个函数这一步干掉呢?

预处理每一块内实际 每个数组位置 的出现次数。

https://www.cnblogs.com/chenyushuo/p/5275753.html

piao来的代码

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
const int smaxn=400;
int n,q,l[maxn],r[maxn],opt,u,v,b[maxn];

namespace bit{
    unsigned long long s[maxn];
    int lowbit(int x) {return x&(-x);} 
    void add(int id,int val){
        for(int i=id;i<=n;i+=lowbit(i)){  //注意这里的n,需要根据题目要求改
            s[i]+=val;
        }
    }
    unsigned long long query(int id){
        unsigned long long ans=0;
        for(int i=id;i>=1;i-=lowbit(i)){
            ans+=s[i];
        }
        return ans;
    }
    unsigned long long qsum(int l,int r){return query(r)-query(l-1);}
    void clear(){memset(s,0,sizeof(s));}
}
using namespace bit;

namespace blocking{
    int tot,belong[maxn],siz[smaxn],block;  //分块基本数据
    unsigned long long sum[maxn];                //解题数据
    int a[maxn],f[maxn][smaxn],c[maxn];
    void init(int n) {
        block=(int)sqrt((double)n);
        tot=n%block?(n/block+1):(n/block);
        for(int i=1;i<=n;i++) belong[i]=(i-1)/block+1;
        for(int i=1;i<=tot;i++) siz[i]=(min(i*block,n)-(i-1)*block);

        for(int i=1;i<=tot;i++) {
            for(int j=1;j<=n;j++) c[j]=0;
            for(int j=block*(i-1)+1;j<=min(i*block,n);j++) c[l[j]]++,c[r[j]+1]--;
            for(int j=1;j<=n;j++) c[j]+=c[j-1],f[j][i]=c[j];
        }
    }
    void update(int id,int val) {
        add(id,val-a[id]);
        for(int i=1;i<=tot;i++) sum[i]+=1LL*f[id][i]*(val-a[id]);
        a[id]=val;
    }

    unsigned long long query(int u,int v) {
        unsigned long long ans=0;
        if(belong[v]-belong[u]<=1) {
            for(int i=u;i<=v;i++) ans+=qsum(l[i],r[i]);
            return ans;
        }
        for(int i=u;i<=belong[u]*block;i++) ans+=qsum(l[i],r[i]);
        for(int i=belong[u]+1;i<=belong[v]-1;i++) ans+=sum[i];
        for(int i=(belong[v]-1)*block+1;i<=v;i++) ans+=qsum(l[i],r[i]);
        return ans;
    }

    void debug() {
        printf("block= %d, tot= %d\n",block,tot);
        for(int i=1;i<=n;i++) printf("%d%c",belong[i],i==n?'\n':' ');
    }
}
using namespace blocking;

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&b[i]);
    for(int i=1;i<=n;i++) scanf("%d %d",&l[i],&r[i]);
    init(n);
    for(int i=1;i<=n;i++) update(i,b[i]);
    scanf("%d",&q);
    while(q--) {
        scanf("%d %d %d",&opt,&u,&v);
        if(opt==2) printf("%llu\n",query(u,v));
        else update(u,v);
    }
}

一道类似的题

https://blog.csdn.net/qq_40791842/article/details/100714849

posted @ 2020-08-18 10:15  ke_xin  阅读(37)  评论(0编辑  收藏  举报