分块入门
Posted knife-rose
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了分块入门相关的知识,希望对你有一定的参考价值。
分块就是乱搞(确信
啥是分块
分块本质就是优雅的暴力,通过预处理和根号平衡(玄学地)让复杂度降低
比如我们考虑一个线段树裸题:
区间加,区间查询,(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(nsqrt{n}))
虽然相比(n^2)很优秀,但是为啥我不写线段树呢???
相比其他数据结构优势
众所周知,线段树维护的信息需要能够区间快速合并,但是分块由于过于暴力,可以忽略这个条件
如这道例题:
区间加,区间小于(k)的个数
线段树已经去世,但是分块仍然可以胜任:
预先将每个块内数字排序,对于修改操作,显然不会影响整块的有序性,对于散块我们可以暴力重构,复杂度(O(sqrt{n}logn))
对于查询操作,可以散块暴力查询,整块二分查找,复杂度(O(sqrt{n}logn))
总复杂度(O(nsqrt{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
",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(nsqrt{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)的分析方法:对于非同一数字块暴力计算,对于同一数字块
……好像有点锅,先咕咕咕了
以上是关于分块入门的主要内容,如果未能解决你的问题,请参考以下文章