分块入门

分块,一直以来都听过这个,但是从来没有用过,发现其实很多题目都可以用分块卡过去,突然想起来学分块的原因也是因为两周前的西安邀请赛有一题,看到网上有人说用分块可以做,而且貌似很裸? 心想要是当时会的话就金了啊。(或者把当时脑抽没想到的线段树做出来TAT)

不说废话了,直接进入正题,分块,顾名思义,就是把很长的一整个区间,分成几个小的区间去处理,降低时间复杂度。(其实就和莫队思想差不多)

那么我们究竟分成多少块呢?答案是nn

因为这样我们分出来的每一段区间的数字个数就是nn

区间个数和每个区间所维护的数字个数相等,所以时间复杂度是比较优的。

比如n=100的时候,分成了10个区间,每个区间维护10个数。
有人问n=105的时候怎么办,那我们就多分一个区间,共11个区间,前10个区间每个区间维护10个数,第11个区间维护5个数就好了(换句话说就是余数单独分一个区间,并且可知余数不超过nn)。

我们先看一道裸题。

初始时给你n个数字(n<=100000n<=100000),至多q次操作(q<=100000q<=100000),操作分为两种:
1 x y表示将第x个数字加上y
2 x y表示求[x,y]区间的最大值

有人一看卧槽这不是线段树或树状数组裸题吗?是的,线段树和树状数组完全可以做,但是分块怎么做呢?

可能大家一下子就想出来了,我们将n个数字分成nn个区间之后,每个区间维护一个最大值,在第一种操作的时候,我们修改一下单点的值并维护所在的区间,在第二种操作时,我们扫描一遍从x到y的区间,注意,我们并不是暴力地扫描每一个点,而是如果某个区间被包括在[x,y]的范围内,我们就直接看区间维护的最大值就可以了,而对于只有一部分在[x,y]范围内的区间,我们就暴力看每个数就行了。

我们可以推出这样做时间复杂度是nnnn

为什么呢,单点更新的时候,显然是O(1)O(1)
而查询的时候,对于全部包含在[x,y]中的区间,我们至多需要查看nn个区间,而对于不完全被包含在[x,y]中的区间,我们暴力地去查看,也只需要看至多两个区间(左端点和右端点所在的区间),共2(n1)2(n−1)个数,所以总的时间复杂度还是nn

nn次操作,那就是O(nn)

我们可以看到这个时间复杂度是不如线段树O(nlogn)O(nlogn)的,但是也是可以接受,并且能过很多题的。

具体代码怎么写呢?这里参考卿学姐的写法(orz)。

#include <iostream>
#include <math.h>
#include <string.h>
#include <stdio.h>
#include <algorithm>

using namespace std;
const int maxn = 100050;
int n,q,block,l[maxn],r[maxn],num,belong[maxn],Max[maxn];
//block表示每个区间所维护的数的个数,num表示区间个数
//l[i]和r[i]表示第i个区间的左右端点,belong[i]表示第i个值所在的区间
//Max[i]表示第i个区间的最大值
int a[maxn];
void build()
{
    int block=sqrt(n);
    int num=n/block;
    if(n%block)//如果有余数则需要另开一个区间
        num++;
    for(int i=1;i<=num;i++)
    {
        l[i]=(i-1)*block+1;
        r[i]=i*block;
    }
    r[num]=n;//最后一个区间右端点特殊处理
    memset(Max,0,sizeof(Max));
    for(int i=1;i<=n;i++)//初始化Max数组
    {
        belong[i]=(i-1)/block+1;
        Max[belong[i]]=max(Max[belong[i]],a[i]);
    }
}

void update(int pos,int y)
{
    a[pos]+=y;
    Max[belong[pos]]=max(Max[belong[pos]],a[pos]);
}
int query(int x,int y)
{
    int ll=belong[x];
    int rr=belong[y];
    if(ll==rr)//x和y在同一个区间的情况
    {
        int ans=0;
        for(int i=x;i<=y;i++)
            ans=max(ans,a[i]);
        return ans;
    }
    else
    {
        int ans=0;
        for(int i=x;i<=r[ll];i++)//处理x所在的区间
            ans=max(ans,a[i]);
        for(int i=ll+1;i<=rr-1;i++)//中间的区间
            ans=max(ans,Max[i]);
        for(int i=l[rr];i<=y;i++)//处理y所在的区间
            ans=max(ans,a[i]);
        return ans;
    }
}
int main()
{
    scanf("%d%d",&n,&q);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    build();
    while(q--)
    {
        int t,x,y;
        scanf("%d%d%d",&t,&x,&y);
        if(t==1)
            update(x,y);
        else
            printf("%d\n",query(x,y));
    }
    return 0;
}

这样一道简单的分块就写完了
也是今天刚会的一道算法,讲得比较浅,以后有机会再写分块。

### 数列分块入门第8题的算法实现与解析 数列分块是一种高效的处理区间查询和修改的技术,其核心思想是将数组划分为若干个连续的小块,每一块内的元素可以快速更新或查询。对于数列分块入门第8题,假设问题是涉及区间的加法操作以及最大值查询,则可以通过以下方法来解决。 #### 1. 数据结构设计 为了高效完成区间加法和最大值查询的操作,我们可以维护两个辅助数组: - `block_sum[]`:存储每个块的最大值。 - `lazy_tag[]`:标记每个块是否有延迟更新(即尚未应用到具体元素上的增量)。 这些数据结构的设计使得我们可以在 $ O(\sqrt{n}) $ 的时间复杂度下完成单次操作[^3]。 #### 2. 初始化过程 初始化时,我们需要计算初始状态下的块划分情况,并填充上述辅助数组的内容。以下是具体的代码实现: ```cpp #include <bits/stdc++.h> using namespace std; const int MAXN = 1e5 + 5; int a[MAXN], block_size, block_num; long long block_max[400]; bool lazy_flag[400]; void build_block(int n) { block_size = sqrt(n); block_num = (n + block_size - 1) / block_size; memset(block_max, 0, sizeof(block_max)); memset(lazy_flag, false, sizeof(lazy_flag)); for (int i = 0; i < n; ++i) { int idx = i / block_size; block_max[idx] = max(block_max[idx], (long long)a[i]); } } ``` #### 3. 延迟标记的应用 当执行区间加法时,为了避免逐一遍历整个范围中的每一个元素,引入懒惰传播机制。如果某个整块完全被覆盖在当前操作范围内,则直接对该块打上标签并记录增量;否则逐一访问该块内部受影响的部分。 下面是针对这一逻辑的具体函数定义: ```cpp // Apply the pending update to all elements within specified block. void propagate(int blk_idx, int delta) { if (!lazy_flag[blk_idx]) return; // Update maximum value of this block accordingly. block_max[blk_idx] += delta * block_size; lazy_flag[blk_idx] = false; } // Add 'delta' to range [l, r]. void add_range(int l, int r, int delta, int n) { int start_blk = l / block_size, end_blk = r / block_size; if (start_blk == end_blk) { for (int i = l; i <= min(r, (start_blk + 1) * block_size - 1); ++i) a[i] += delta; // Recalculate new maximum after modification. block_max[start_blk] = 0; for (int i = start_blk * block_size; i < ((start_blk + 1) * block_size && i < n); ++i) block_max[start_blk] = max((long long)a[i], block_max[start_blk]); } else { // Process first incomplete block separately. for (int i = l; i < (start_blk + 1) * block_size; ++i) a[i] += delta; block_max[start_blk] = 0; for (int i = start_blk * block_size; i < ((start_blk + 1) * block_size && i < n); ++i) block_max[start_blk] = max((long long)a[i], block_max[start_blk]); // Fully covered blocks can simply apply tag updates. for (int b = start_blk + 1; b < end_blk; ++b){ block_max[b] += delta * block_size; lazy_flag[b] |= true; } // Handle last partial block similarly as above case. for (int i = end_blk * block_size; i <= r; ++i) a[i] += delta; block_max[end_blk] = 0; for (int i = end_blk * block_size; i < ((end_blk + 1) * block_size && i < n); ++i) block_max[end_blk] = max((long long)a[i], block_max[end_blk]); } } ``` #### 4. 查询最值功能 最后一步是在给定区间内查找最大的数值。这同样依赖于之前构建好的块级信息来进行加速检索。 ```cpp long long query_max(int l, int r) { int start_blk = l / block_size, end_blk = r / block_size; long long result = LLONG_MIN; if (start_blk == end_blk) { for (int i = l; i <= r; ++i) result = max(result, (long long)a[i]); } else { for (int i = l; i < (start_blk + 1) * block_size; ++i) result = max(result, (long long)a[i]); for (int b = start_blk + 1; b < end_blk; ++b) result = max(result, block_max[b]); for (int i = end_blk * block_size; i <= r; ++i) result = max(result, (long long)a[i]); } return result; } ``` 通过以上步骤即可有效应对数列分块相关的题目需求。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值