专题5-1 分块:数组分块
http://hzwer.com/8053.html 此博客有部分借鉴内容,对原作者表示感谢
PART 1 分块简介
对于分块,许多人可能不陌生,毕竟这是一个顾名思义的词语。
不过,作为一个教学贴,有必要和大家介绍一下分块的概念。
分块,顾名思义就是将要求的东西分成若干个部分,对于特定的区间进行特定的操作
老子曾说过,任何事情都有联系
老子:“我没有说过。”
线段树和分块有相似之处,我们来类比一下
可以看出,对于一个1-5的数组,我们把它分成若干部分,直到叶节点所代表的区间大小为1
综合时间空间复杂度考虑,我们每次把区间二分,所需要的代价最小
如果我们现在要对1-4的区间进行统一操作,按照朴素算法,我们会对1-4中间的每一个区间都进行一次操作。
听起来不错,但是如果有100000次操作,100000个元素呢?这波肯定要爆掉。
于是,按照我们上面进行的操作,我们只需对1-3区间和4-4区间进行操作就可以了。
这就包含了分块的思想,分而治之
PART 2 分块简单举例
综上所述,我们要对数组进行分块,从而提高它的时间空间效率
Q1 给出一个长为n的数列,以及m个操作,操作涉及区间加法,单点查值。
对于这个题目,我们利用上面的操作,将数组进行以下操作
1.将数组分成k块
2.对于一个区间 [ l , r ] 来说,如果我们对其进行区间加法操作,我们就要分成两种操作
(1)如果说我们的边界 l 和 r 都在区间端点上,那就好办了,我们只需要给这些被包括的区间增加一个标记即可。
如果我们设定一个tag数组,存储一个数是否有过修改
但是,这里的tag数组肯定不能直接以元素下标进行创建,如果这样就和朴素算法没有什么区别,在这里,就是我们优化的精髓之一
我们用 bl [ i ] 存储下标为 i 的元素在第几个块内,如果我们要对一个区间进行操作,我们只要把 tag [ bl [ i ] ] 进行一次操作即可
(2)如果我们的边界 l 和 r 在不在边界而各在一个区间内呢?
对于 l 和 r 之间的区间(也就是第二部分),我们仍然可以采用类似(1)中的方法,接下来操作的就是对于 l 到 l 所在区间的终点和 r 区间的起点到 r 这些(也就是第一和第三部分),我们只要暴力操作就可以了
这样,我们的问题就解决了
等等,还有一个呢? 不是把数组分成k块吗?k怎么取?
根据各种各样的不等式,我们可以认为,当k=√n(根号n)的时候,时间空间复杂度代价最低
//数组分块1 区间修改,单点查询
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;
int n,kt,m;
int v[50005],bl[50005],atag[50005];
void add(int a,int b,int c)
{
for(int i=a;i<=min(bl[a]*kt,b);i++)//bl[a]*blo为a所在区间的终位,b为要替换的终位
//这是对首区间的操作
v[i]+=c;
if(bl[a]!=bl[b])
for(