zoukankan      html  css  js  c++  java
  • 【分块入门】分块

    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

     

  • 相关阅读:
    需求墙,挺有创意
    产品分析套路
    复杂问题的拆解分析,有点意思
    xss如何加载远程js的一些tips
    一次有趣的XSS漏洞挖掘分析(3)最终篇
    一次有趣的XSS漏洞挖掘分析(2)
    一次有趣的XSS漏洞挖掘分析(1)
    Java和WebSocket开发网页聊天室
    Socket.IO聊天室~简单实用
    C语言基于GTK+Libvlc实现的简易视频播放器(二)
  • 原文地址:https://www.cnblogs.com/lxyyyy/p/11271333.html
Copyright © 2011-2022 走看看