码迷,mamicode.com
首页 > 其他好文 > 详细

分块入门

时间:2019-12-11 13:11:06      阅读:98      评论:0      收藏:0      [点我收藏+]

标签:print   read   格式   upd   turn   怎么   就会   tchar   细节   

分块就是乱搞(确信

啥是分块

分块本质就是优雅的暴力,通过预处理和根号平衡(玄学地)让复杂度降低

比如我们考虑一个线段树裸题:

区间加,区间查询,\(n<=1e5\)

显然暴力的做法是\(n^2\)的,那么我们有没有什么优化方法呢?

我们可以将整个序列分为若干块,提前预处理出每个块的和,每次修改如果包含一个整块就直接打标记,非整块范围就暴力修改

时间复杂度

假设我们将\(k\)个元素分成一块,那么将一共分出\(\frac{n}{k}\)个块

对于修改和查询操作:整块\(O(1)\)标记或查询,散块暴力修改,那么一次操作复杂度最高为\(O(\frac{n}{k}+k)\)

根据均值不等式,当\(\frac{n}{k}=k\)时,\(\frac{n}{k}+k\)最小,也就是说当\(k=\sqrt{n}\)时总复杂度最低为\(O(n\sqrt{n})\)

虽然相比\(n^2\)很优秀,但是为啥我不写线段树呢???

相比其他数据结构优势

众所周知,线段树维护的信息需要能够区间快速合并,但是分块由于过于暴力,可以忽略这个条件

如这道例题:

区间加,区间小于\(k\)的个数

线段树已经去世,但是分块仍然可以胜任:

预先将每个块内数字排序,对于修改操作,显然不会影响整块的有序性,对于散块我们可以暴力重构,复杂度\(O(\sqrt{n}logn)\)

对于查询操作,可以散块暴力查询,整块二分查找,复杂度\(O(\sqrt{n}logn)\)

总复杂度\(O(n\sqrt{n}logn)\)

分块牺牲了部分复杂度,所以处理信息更加灵活,保留的信息量更多

入门例题

就放黄学长的分块入门吧

分块入门1:区间加,单点查询

没什么好说的了,放代码康康格式吧


展开查看

#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int read()
{
    int x=0,f=1;
    char ch;
    for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar());
    if(ch=='-') f=0,ch=getchar();
    while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
    return f?x:-x;
}
int n,cnt;
int a[100010];
int bel[100010],tag[100010];
inline void update(int l,int r,int k)
{
    int lx=bel[l],rx=bel[r];
    for(int i=l;bel[i]==lx&&i<=r;++i)
        a[i]+=k;
    if(lx==rx) return ;
    for(int i=r;bel[i]==rx;--i)
        a[i]+=k;
    for(int i=lx+1;i<rx;++i)
        tag[i]+=k;
}
signed main()
{
    n=read();
    cnt=sqrt(n);
    for(int i=1;i<=n;++i)
    {
        a[i]=read();
        bel[i]=(i-1)/cnt+1;
    }
    for(int t,x,y,z,i=1;i<=n;++i)
    {
        t=read(),x=read(),y=read(),z=read();
        if(!t)
            update(x,y,z);
        else
            printf("%lld\n",a[y]+tag[bel[y]]);
    }
return 0;
}


分块入门2:区间加,区间小于\(k\)个数

刚才讲过惹\(qwq\)

分块入门3:区间加,区间\(k\)前驱

\(emmm\)和上一题方法一样,懒得讲了

分块入门4:区间加,区间求和

也讲过了……(这样看来这篇博客画风清奇

分块入门5:区间求和,区间开方(下取整)

这个有点意思

首先区间求和肯定要预处理出块内和,那对于区间开方怎么处理呢?

这个题目有个条件,\(a_i<=maxint\),我们可以算出对于每个数字最多开方\(6\)次就会变成\(1\)\(0\),然后再也不会变化了

所以如果一个块内全部变为了\(1\)或者\(0\),就不用再处理了,那么对于块内存在非\(1\)\(0\)的块,暴力重构,对于已经不会变化的块直接打\(continue\)标记然后跳过

分块入门6:单点插入,单点查询(数据随机)

考虑到数据随机,对每个块开个链表,记录下每个块内有目前多少个元素,然后模拟就好了

如果数据不随机怎么办?我们可以设置一个值s,每当插入s个数字就对整个序列重新分块,可以证明理论复杂度仍然是\(O(n\sqrt{n})\)级别的

分块入门7:区间加法,区间乘法,区间求和

这不线段树\(2\)吗(雾

我在这里再说明一下标记顺序的问题:一定要先乘再加!

先加后乘效果如下:

假设当前状态为\((a+add)*mul\),又添加了一组\([add2,mul2]\)标记

当前状态变为\(((a+add)*mul+add2)*mul2\)

展开\(((a+add)*mul*mul2+add2*mul2)\)

继续展开\((a*mul*mul2+add*mul*mul2+add2*mul2)\)

恢复标记格式:\((a+add+(\frac{add2}{mul}))*mul*mul2\)

发现了什么?分数!这样可能会丢精度\(qwq\)

如果我们先乘后加呢

\((a*mul+add)添加新标记\)[add2,mul2]$

当前状态变为\(((a*mul)+add)*mul2+add2\)

展开\((a*mul*mul2+add*mul2+add2)\)

恢复标记格式:\((a*mul*mul2)+add*mul2+add2\)

完美

注意细节:对散块处理之前要下放整个散块标记

分块入门8:查询区间等于\(k\)的个数,区间修改

似乎可以沿用分块入门\(2\)的套路,不过很不幸被卡了

考虑入门\(5\)的分析方法:对于非同一数字块暴力计算,对于同一数字块

……好像有点锅,先咕咕咕了

分块入门

标签:print   read   格式   upd   turn   怎么   就会   tchar   细节   

原文地址:https://www.cnblogs.com/knife-rose/p/12021693.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!