数列分块 笔记

我上集训的前3天都是 h z w e r hzwer hzwer在讲课。不知为什么, h z w e r hzwer hzwer老师特别喜欢分块,拿分块给我们洗脑,说什么:“主席树,线段树,平衡树,都不会,就用分块”。

那我就来讲讲分块⑧。那么,什么是分块呢?

主体思想

我们知道,有一些暴力是修改 O ( 1 ) O(1) O(1),求和 O ( n ) O(n) O(n)(【模板】数组),有一些操作是修改 O ( n ) O(n) O(n),求和 O ( 1 ) O(1) O(1)(【模板】前缀和),但是我们能不能平衡一下呢?

树状数组&线段树 是做到了 O ( l o g n ) O(logn) O(logn),两个都是。那么,有没有更暴力,但是有不那么暴力,而且好写的做法呢?那就是分块了。图示:

把序列分成 n \sqrt{n} n 个块。然后我们怎么做操作呢?比如说区间求和&区间加法操作?

(Q:woc这个是讲线段树要讲好久才讲到的操作诶?真的现在就讲?)
(A:不怕,分块比线段树好写的多)

比如说我们要查询红色部分的和。对于跨过的完整快(绿色部分),我们直接维护块里的和,
然后加上即珂。对于蓝色部分,我们就暴力求和,反正也不多,最多 2 n 2\sqrt{n} 2n 个。所以就保证了一次求和的复杂度是 O ( n ) O(\sqrt{n}) O(n )

对于加上红色的部分,我们只要对块内标记做修改即珂。然后对于蓝色的部分,我们只要暴力 + = += +=就珂以了。这样也是 O ( n ) O(\sqrt{n}) O(n )的。

总的来说,分块的思想就是:

完 整 块 标 记 , 不 完 整 暴 力 \color{#ff0000}完整块标记,不完整暴力

来题!

h z w e r hzwer hzwer l i b r e o j libreoj libreoj上发了不少分块的入门题目,找“分块”,有且只有那几个题。由于我也是刚学,就找几个水的玩玩吧。

数列分块2
题意:支持两种操作,区间加,区间查询多少数 &lt; = x &lt;=x <=x
做法:

分块。块维护一个标记,表示这个块被整体加了多少。对于区间加的操作就按上面的方法来就珂以了。

区间查询多少数 &lt; = x &lt;=x <=x,我们发现,对于一个块,这个块内部的顺序是不会影响整体答案的。所以我们把原数组拷贝一个副本,然后对于同一个块,就排一下序。对于整个块,就只要 l o w e r b o u n d lower_bound lowerbound一下就能求出答案了。对于不完整的块,我们就用原来的数组,暴力算一下即珂。

代码:

#include<bits/stdc++.h>
using namespace std;
namespace Flandle_Scarlet
{
    #define N 100100
    #define int long long
    void R1(int &x)
    {
        x=0;char c=getchar();int f=1;
        while(c<'0' or c>'9') f=(c=='-')?-1:1,c=getchar();
        while(c>='0' and c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
        x=(f==1)?x:-x;
    }
    int n,a[N];
    int block;
    void Input()
    {
        R1(n);
        block=sqrt(n+0.5);
        for(int i=1;i<=n;++i) R1(a[i]);
    }

    int tag[N];//加法标记
    vector<int> v[1010];//原数组的拷贝,为了方便分块,用了vector
    #define pos(x)     ((x-1)/block+1)
    #define Start(b)   ((b-1)*block+1)
    #define End(b,lim) (min(b*block,lim))
    //很多时候End都是要限制一下的,所以加上了lim
    void cls(int x)
    {
        v[x].clear();
        for(int i=Start(x);i<=End(x,n);++i)
        //比如说这里最后一个块不能超出n,所以就要和n取min
        {
            v[x].push_back(a[i]);
        }
        sort(v[x].begin(),v[x].end());//排序
    }
    void Add(int l,int r,int val)
    {
        for(int i=l;i<=End(pos(l),r);++i) a[i]+=val;
        cls(pos(l));//暴力
        if (pos(l)!=pos(r))//这里判这个的原因是,
        //如果l和r同一个块,那么就只会有一段不是完整的块,就会算重
        {
            for(int i=Start(pos(r));i<=r;++i)
            {
                a[i]+=val;
            }
            cls(pos(r));
        }

        for(int i=pos(l)+1;i<=pos(r)-1;++i)
        {
            tag[i]+=val;
        }//整个块就打标记
    }
    int Query(int l,int r,int k)
    {
        int ans=0;
        for(int i=l;i<=End(pos(l),r);++i)//暴力统计
        {
            if (a[i]+tag[pos(l)]<k) ++ans;
        }
        if (pos(l)!=pos(r))
        {
            for(int i=Start(pos(r));i<=r;++i)
            {
                if (a[i]+tag[pos(r)]<k) ++ans;
            }
        }
        for(int i=pos(l)+1;i<=pos(r)-1;++i)
        {
            int x=k-tag[i];//注意:要k-tag[i],原因是这个块里被加上了tag[i],但是并没有体现在副本数组v中
            ans+=lower_bound(v[i].begin(),v[i].end(),x)-v[i].begin();
        }
        return ans;
    }
    void Soviet()
    {
        for(int i=1;i<=n;++i)
        {
            v[pos(i)].push_back(a[i]);
        }
        for(int i=1;i<=pos(n);++i)
        {
            sort(v[i].begin(),v[i].end());
        }
        for(int i=1;i<=n;++i)
        {
            int o,l,r,c;
            R1(o),R1(l),R1(r),R1(c);
            if (o==0)
            {
                Add(l,r,c);
            }
            else
            {
                printf("%lld\n",Query(l,r,c*c));
            }
        }
    }
    void IsMyWife()
    {
        Input();
        Soviet();
    }
    #undef int //long long
};
int main()
{
    Flandle_Scarlet::IsMyWife();
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值