归纳(三):分块

何为分块

优雅的暴力

思维难度低下,代码难度低下,非常优秀的一种算法(?)。
实现方法主要是块与块之间 \(O(1)*\sqrt{n}\) 查询,边角 \(O(\sqrt{n})\) 暴力查询。
总复杂度 \(O(\sqrt{n})\)

代码实现

首先需要块的大小 \(block\) ,和每个下标归属于哪个块 \(belong[i]\)
如果需要块内有序,可以使用 \(std::vector\)

区间修改对于整块使用lazy标记的思想,边边角角还是\(O(\sqrt{n})\)暴力。

查询同理。

例题(一):教主的魔法

可以说,这是分块最最经典的一道题了。
因为似乎没有其他做法?

链接:教主的魔法

分好块,用 \(std::vector<int> vc[maxn]\) 维护区间的高矮关系。
\(std::lower_bound\) 进行查询。

修改就打标记。

#include<cstdio>
#include<vector>
#include<cmath>
#include<algorithm>

const int maxn=1e6+5;

class Divid_Block {

    private:

        int n,q,delta[maxn],a[maxn];
        int block,belong[maxn],num;
        std::vector<int> vc[maxn];

        void update(int x) {
            vc[x].clear();
            for(int i=(x-1)*block+1;i<=x*block;i++)
                vc[x].push_back(a[i]);
            std::sort(vc[x].begin(),vc[x].end());
        }

        void modify(int l,int r,int c) {
            for(int i=l;i<=std::min(r,belong[l]*block);i++)
                a[i]+=c;
            update(belong[l]);
            if(belong[l]!=belong[r]) {
                for(int i=(belong[r]-1)*block+1;i<=r;i++)
                    a[i]+=c;
                update(belong[r]);	
            }
            for(int i=belong[l]+1;i<belong[r];i++)
                delta[i]+=c;
        }

        int query(int l,int r,int c) {
            int ans=0;
            for(int i=l;i<=std::min(r,belong[l]*block);i++)
                if(a[i]+delta[belong[l]]>=c) ++ans;
            if(belong[l]!=belong[r])
                for(int i=(belong[r]-1)*block+1;i<=r;i++)
                    if(a[i]+delta[belong[r]]>=c) ++ans;
            for(int i=belong[l]+1;i<belong[r];i++)
                ans+=block-(std::lower_bound(vc[i].begin(),vc[i].end(),c-delta[i])-vc[i].begin());
            return ans;
        }

    public:

        int work() {
            scanf("%d%d",&n,&q);
            block=sqrt((n+2)/3);
            for(int i=1;i<=n;i++) {
                scanf("%d",a+i);
                belong[i]=(i-1)/block+1;
                vc[belong[i]].push_back(a[i]);
                if(i%block==1) ++num;
            }
            for(int i=1;i<=num;i++) std::sort(vc[i].begin(),vc[i].end());
            while(q--) {
                char opt=getchar();
                while(opt!='A' && opt!='M') opt=getchar();
                int lf,rg,c;scanf("%d%d%d",&lf,&rg,&c);
                if(opt=='A') printf("%d\n",query(lf,rg,c));
                else modify(lf,rg,c);
            }
            return 0;
        }
}T;

int main() {return T.work();}

例题(二):弹飞绵羊

其实,只要你没学过CT,这道题还是很有希望做出来的。
记录两个信息:
\(tim[i]\)\(whe[i]\) 表示:需要跳几次才能出这个块,出了这个块会到哪个点上。

每一次修改弹力系数,最多只会影响本块内会跳到这个点上的弹簧。

所以每一次修改就重构块。

于是就欢乐的解决了这道题。

(至今还没写对LCT的我就靠这个安慰自己)

#include<bits/stdc++.h>

const int maxn=2e5+5;

class Divid_Block {
    private:

        int a[maxn];
        int belong[maxn],whe[maxn],tim[maxn];
        int block,n,m;

        inline int read() {
            int x;char ch;while(!isdigit(ch=getchar()));
            for(x=ch-'0';isdigit(ch=getchar());x=x*10+ch-'0');
            return x;
        }

        void build() {
            block=sqrt((n+2)/3);
            for(int i=1;i<=n;i++)
                belong[i]=(i-1)/block+1;
            belong[n+1]=belong[n]+1;
            for(int i=n;i;i--) {
                whe[i]=i+a[i];
                int r=block*belong[i]>n?n:block*belong[i];
                if(whe[i]>r) tim[i]=1;
                else tim[i]=tim[whe[i]]+1,whe[i]=whe[whe[i]];
            }
            return ;
        }

        void modify(int pos,int val) {
            a[pos]=val;
            for(int i=block*belong[pos];i>=block*(belong[pos]-1);i--) {
                whe[i]=i+a[i];
                if(whe[i]>block*belong[pos]) tim[i]=1;
                else tim[i]=tim[whe[i]]+1,whe[i]=whe[whe[i]];
            }
        }

        int query(int pos) {
            int ans=0;
            while(pos<=n) ans+=tim[pos],pos=whe[pos];
            return ans;
        }

    public:

        int work() {
            n=read();
            for(int i=1;i<=n;i++) a[i]=read();
            build();
            m=read();
            while(m--) {
                int opt=read(),pos=read();
                ++pos;
                if(opt-1) {
                    int k=read();
                    modify(pos,k);
                }
                else printf("%d\n",query(pos));
            }
            return 0;
        }
}T;

int main() {return T.work();}

注意事项

关于块的大小,可以参见初中dalao的博客(我的分块他教的)

关于分块最优块大小的思考

还有他写的那个上了洛咕日报的博客:

浅谈基础根号算法——分块

lhy %%% 这个 \((A+C)/2\) 在我们机房里天天吊虐我。

分块适用范围很广,基本仅次于 \(n^{2}\) 暴力。

走投无路时可以考虑哦。

posted @ 2018-10-14 11:49  LoLiK  阅读(211)  评论(0编辑  收藏  举报