分块思想讲解 + AcWing 243. 一个简单的整数问题2

分块

分块 是一种思想,而且它其实是一种基于暴力的朴素思想

具体做法:将原数组 分成 sqrt(n),这样一来,每一段的长度和段的个数都是 sqrt(n),分到的 最后一段 根据 “实际剩余多少就是多少” 的原则来划分

现在我们需要 选择一段区间,并将 整个区间都加上 d,假设这段区间 既包含中间若干完整段完整段的个数是 O(sqrt(n)) 级别),又包含 左右两端非完整段(非完整段中元素个数也是 O(sqrt(n)) 级别)。

也就是说,任何一个区间,我们都可以将它划分为:少于 sqrt(n) 个完整段 + 左右 2 个长度小于 sqrt(n) 的非完整段

根据上面这一点,我们直观上感觉,可以 将操作的复杂度也同时降到 O(sqrt(n)) 这一级别。(分块的优化之处

具体操作的时候,我们按照 区间的 “完整性” 和 “非完整性” 来分类 进行操作。

具体操作:

将原序列 分为 sqrt(n) 之后考虑 每一段记录哪些信息,由于修改的时候我们需要 给很长的一段区间加上一个值,为了 提高效率,我们可以借助之前学过的 “懒标记” 的思想

对于 修改 操作,在 中间的完整段 上,我们要记录一种 信息 add(相当于一个懒标记)

  • 表示:本段中的所有数 都要 加上 add。 比如:完整段中 有一个 元素 a[i],那么 其真实值应该为 a[i] + add

对于 查询 操作,由于我们要 快速求得某一完整段的总和,因此我们可以用 另外的变量 sum 存储完整段的总和

  • 表示 本完整段的真实和,意思就是 已经把 add 算上了,这里涉及到的 时间顺序 要明确好

(1)修改:O(sqrt(n))

  • 对于 完整段,当 整体加上一个值 d,我们要进行的 操作 是:add = add + d,和 sum = sum + d * lenlen 表示完整段的长度)(O(sqrt(n))

  • 对于 非完整段(段内),由于 非完整段长度 不超过 sqrt(n),因此 求和的时候 直接 暴力 即可,即 枚举段内所有数,需要进行的 操作a[i] = a[i] + d,同时 总和 sum = sum + dO(sqrt(n))注意,此时 不要对 add 进行修改

(2)查询:O(sqrt(n))

  • 对于 完整段,直接累加 每一个完整段的 sum(因为 sum 存的即为每一个完整段的真实和)(O(sqrt(n))

  • 对于 非完整段(段内)暴力枚举每个元素求和(O(sqrt(n))
    在这里插入图片描述

小结一下,我们发现 分块的思想 还是比较简单的,并没有 树状数组和线段树 那样复杂:

  • 每一块 的长度 分到足够短,这样一来,段内(非完整段,长度 sqrt(n))就可以 暴力求解完整段数量为 sqrt(n))用 懒标记 进行维护。

例题:AcWing 243. 一个简单的整数问题2

在这里插入图片描述
在这里插入图片描述

题意:

对一个 长度为 n 的序列 快速实现两个操作:① 给一段区间加一个数,② 查询某段区间的和

思路:

分块

时间复杂度:

O ( m ∗ s q r t ( n ) ) O(m * sqrt(n)) O(msqrt(n))

代码:

#define _CRT_SECURE_NO_WARNINGS 1
#include <bits/stdc++.h>

using namespace std;
//#define int long long
//#define map unordered_map
typedef long long ll;
const int N = 1e5 + 10, M = 350;
int n, m;
int a[N];
ll sum[M], add[M];
int len;

int get(int i)
{
    return i / len;
}

ll ask(int l, int r)
{
    ll res = 0;
    int L = get(l), R = get(r);
    if (L == R)
    {
        for (int i = l; i <= r; ++i)
        {
            int I = get(i);
            res += (a[i] + add[I]);
        }
    }
    else
    {
        int i = l, j = r, I, J;
        while ((I = get(i)) == L) {
            res += (a[i] + add[I]), ++i;
        }
        while ((J = get(j)) == R) {
            res += (a[j] + add[J]), --j;
        }
        for (int k = I; k <= J; ++k) {
            res += sum[k];
        }
    }
    return res;
}

void modify(int l, int r, int d)
{
    int L = get(l), R = get(r);
    if (L == R)
    {
        for (int i = l; i <= r; ++i)
        {
            int I = get(i);
            a[i] += d, sum[I] += d;
        }
    }
    else
    {
        int i = l, j = r, I, J;
        while ((I = get(i)) == L) {
            a[i] += d, sum[I] += d, ++i;
        }
        while ((J = get(j)) == R) {
            a[j] += d, sum[J] += d, --j;
        }
        for (int k = I; k <= J; ++k){
            sum[k] += (len * d), add[k] += d;
        }
    }
}

signed main()
{
    cin >> n >> m;
    len = n / sqrt(n);
    for (int i = 1; i <= n; ++i)
    {
        int I = get(i);
        scanf("%d", &a[i]);
        sum[I] += a[i];
    }

    while (m--)
    {
        char op[2]; int l, r, d;
        scanf("%s%d%d", op, &l, &r);
        if (*op == 'Q')
        {
            printf("%lld\n", ask(l, r));
        }
        else
        {
            scanf("%d", &d);
            modify(l, r, d);
        }
    }

    return 0;
}

(加注释)

#define _CRT_SECURE_NO_WARNINGS 1
#include <bits/stdc++.h>

using namespace std;
//#define int long long
//#define map unordered_map
typedef long long ll;
const int N = 1e5 + 10, M = 350;
int n, m;
int a[N];
ll sum[M], add[M];
int len;

int get(int i)//映射 将数组中下标为 i 的元素 映射到哪一个完整块
{
    return i / len;//i -> i / len
}

ll ask(int l, int r)
{
    ll res = 0;
    int L = get(l), R = get(r);
    if (L == R)//段内直接暴力
    {
        for (int i = l; i <= r; ++i)
        {
            int I = get(i);
            res += (a[i] + add[I]);
        }
    }
    else
    {
        int i = l, j = r, I, J;
        while ((I = get(i)) == L) {
            res += (a[i] + add[I]), ++i;
        }
        while ((J = get(j)) == R) {
            res += (a[j] + add[J]), --j;
        }
        for (int k = I; k <= J; ++k) {
            res += sum[k];
        }
    }
    return res;
}

void modify(int l, int r, int d)
{
    int L = get(l), R = get(r);
    //分为两类
    if (L == R)//如果 l、r 在同一完整块内,直接暴力即可
    {
        for (int i = l; i <= r; ++i)
        {
            int I = get(i);
            a[i] += d, sum[I] += d;
        }
    }
    else//否则
    {
        int i = l, j = r, I, J;
        //先算一下左右两端的非完整段
        while ((I = get(i)) == L) {//当get(i)==get(l),说明get(i)还是在左边非完整段内,直接暴力即可
            a[i] += d, sum[I] += d, ++i;
        }
        while ((J = get(j)) == R) {
            a[j] += d, sum[J] += d, --j;
        }
        //此时,i、j中间就都是完整段,接下来操作如下
        for (int k = I; k <= J; ++k) {
            sum[k] += (len * d), add[k] += d;
        }
    }
}

signed main()
{
    cin >> n >> m;
    len = n / sqrt(n);//每个完整块的长度定为 sqrt(n)
    for (int i = 1; i <= n; ++i)
    {
        int I = get(i);
        scanf("%d", &a[i]);
        sum[I] += a[i];//每输入一个数 a[i],位置 i 对应完整块总和也要加上 a[i]
    }

    while (m--)
    {
        char op[2]; int l, r, d;
        scanf("%s%d%d", op, &l, &r);
        if (*op == 'Q')
        {
            printf("%lld\n", ask(l, r));
        }
        else
        {
            scanf("%d", &d);
            modify(l, r, d);
        }
    }

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值