• 分块


    分块——优雅的暴力

    参考

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

    分块实现的基本框架:
    划分块(一般将其分为(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
    ",a[r]+tag[b[r]]);
        }
        return 0;
    }
    

    分块入门 2

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

    我们先来思考只有询问操作的情况,角块枚举统计即可;而要在每个整块内寻找小于一个值的元素数,于是我们不得不要求块内元素是有序的,这样就能使用二分法对块内查询,需要预处理时每块做一遍排序,复杂度(O(nlogn)),每次查询在(sqrt n)个块内二分,以及暴力(2sqrt n)个元素,总复杂度(O(nlogn + nsqrt nlogsqrt 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
    ",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
    ",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
    ",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
    ",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
    ",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
    ",block,tot);
            for(int i=1;i<=n;i++) printf("%d%c",belong[i],i==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
    ",query(u,v));
            else update(u,v);
        }
    }
    
    

    一道类似的题

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

  • 相关阅读:
    用自己的语言描述一下程序连接数据库的过程?
    什么是AJAX?
    怎样计算页面执行的时间?
    缓存的几种不同的实现方法?
    <%@Page%>中的Codebehind AytoEventWireup.inherits有何作用?
    上网搜关于缓存的内容,谈谈你对缓存的理解看法。并回答在网站开发中使用缓存有哪些好处?
    在access中如何创建数据库?你认为数据库在网站开发中所扮演的角色是什么?使用数据库和使用文件,两者的优缺点是是什么?
    什么是Cookie对象,Session对象,application对象?
    docker 部署gin項目
    golang windows endless 运行报错 undefined: syscall.SIGUSR1
  • 原文地址:https://www.cnblogs.com/ke-xin/p/13521901.html
Copyright © 2020-2023  润新知