题解:P13976 数列分块入门 1

· · 题解

:::info[题意简述]

原题链接。

:::

分块简介

分块,顾名思义,把数组分成一块一块的。确定块长(即每块的元素个数)s 后,就可以把 a 数组分成这样:(n 表示数组长度)

分好后,就可以进行区间和统计、类似线段树的懒标记。

对于本题,不需要区间和统计,懒标记可以存储整个块都要加的一个数。

时间复杂度分析以及块长的选择

设总长 n,块长 s。显然可以 O(1) 获取一个元素对应块的编号,因此查询操作可以 O(1) 解决。

修改操作分为三个部分:整块之前、整块、整块之后。

综上,修改操作时间复杂度为 O(s+\frac ns),如果 s 太小或者太大都会导致总复杂度变高,通过数学直觉或者图像可以发现当 s=\frac nss=\sqrt n 时复杂度最小,为 O(\sqrt n)

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll maxn = 3e5 + 10, maxs = 550; // 550 >= sqrt(maxn)
ll n, a[maxn], siz, tag[maxs], opt, l, r, c; // siz: 块长, tag: 懒标记
void solve()
{
    cin >> n;
    for (ll i = 1; i <= n; i++)
    {
        cin >> a[i];
    }
    siz = sqrt(n);
    for (ll q = 1; q <= n; q++)
    {
        cin >> opt >> l >> r >> c;
        if (opt)
        {
            cout << a[r] + tag[(r - 1) / siz + 1] << endl; // 记得加上懒标记
        }
        else
        {
            for (; l <= r && l % siz != 1; l++)
            {
                a[l] += c;
            } // 整块之前
            for (; l <= r - siz; l += siz)
            {
                tag[(l - 1) / siz + 1] += c;
            } // 整块
            for (; l <= r; l++)
            {
                a[l] += c;
            } // 整块之后
        }
    }
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    ll t = 1;
    // cin >> t;
    while (t--)
    {
        solve();
    }
    return 0;
}