数列分块入门系列

数列分块入门 1

其实线段树和树状数组都可以做,但是毕竟是在练习分块。

分块,顾名思义把数组分成若干块,这里特指分成 \(\sqrt n\) 块,每块长 \(\sqrt n\)

分块的修改非常暴力,如果一整块都在要修改的区间里,那就一起打个 tag,否则暴力单点修改。

对于整块的修改,一次最多 \(\sqrt n\) 次,而散块显然只会有一头一尾两块,所以次数也是 \(\sqrt n\) 级别的,总复杂 \(\text O(q\sqrt n)\)

int a[N], tag[N], len;
void add(int l, int r, int x)
{
    while (l <= r && l % len != 1) a[l++] += x;
    while (l + len <= r) tag[(l - 1) / len + 1] += x, l += len;
    while (l <= r) a[l++] += x;
}
signed main()
{
    cin.tie (0), cout.tie (0);
    ios :: sync_with_stdio (false);
    int n;
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i];
    len = sqrt (n);
    for (int i = 1; i <= n; i++)
    {
        int opt, l, r, c;
        cin >> opt >> l >> r >> c;
        if (opt == 0) add (l, r, c);
        else cout << a[r] + tag[(r - 1) / len + 1] << "\n";
    }
    return 0;
}

数列分块入门 2 & 教主的魔法*

最经典分块 trick,每一块开一个数组维护 排好序后的样子,区间加时对于整块也是直接打 tag,而散块暴力修改完以后要 重新算一次排好序后的数组。查询时对于散块暴力查询,整块使用二分。记得加上标记。

每次修改是 \(\text O(\sqrt n + \sqrt n\log \sqrt n)\),每次查询是 \(\text O(\sqrt n + \sqrt n\log \sqrt n)\)

int n;
int a[N], len;
int b[510][510], sz[510], tag[510];
void rebuild(int x)
{
    for (int i = (x - 1) * len + 1; i <= min (n, x * len); i++)
        b[x][i - (x - 1) * len] = a[i];
    sort (b[x] + 1, b[x] + sz[x] + 1);
}
void add(int l, int r, int c)
{
    int tl = l, tr = r;
    bool flag1 = false, flag2 = false;
    while (l % len != 1 && l <= r) a[l++] += c, flag1 = true;
    while (l + len <= r) tag[l / len + 1] += c, l += len;
    while (l <= r) a[l++] += c, flag2 = true;
    if (flag1) rebuild ((tl - 1) / len + 1);
    if (flag2) rebuild ((tr - 1) / len + 1);
}
int query(int l, int r, int c)
{
    int cnt = 0;
    while (l % len != 1 && l <= r)
        cnt += ((a[l] + tag[(l - 1) / len + 1]) < c), l++;
    while (l + len <= r)
    {
        int now = (l - 1) / len + 1;
        int x = lower_bound (b[now] + 1, b[now] + sz[now] + 1, c - tag[now]) - b[now] - 1;
        cnt += x, l += len;
    }
    while (l <= r)
        cnt += ((a[l] + tag[(l - 1) / len + 1]) < c), l++;
    return cnt;
}
signed main()
{
    cin.tie (0), cout.tie (0);
    ios :: sync_with_stdio (false);
    cin >> n, len = sqrt (n);
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
        b[(i - 1) / len + 1][++sz[(i - 1) / len + 1]] = a[i];
    }
    for (int i = 1; i <= (n - 1) / len + 1; i++)
        sort (b[i] + 1, b[i] + sz[i] + 1);
    for (int i = 1; i <= n; i++)
    {
        int op, l, r, c;
        cin >> op >> l >> r >> c;
        if (!op) add (l, r, c);
        else cout << query (l, r, c * c) << "\n";
    }
    return 0;
}

数列分块入门 3

和上面相同,只是二分完后取小于的中最大的。但是在洛谷上要注意数据范围,打在块上的标记是可以爆 long long 的。

#define pos(x) ((x - 1) / len + 1)
#define xth(x) ((x - 1) % len + 1)
typedef long long ll;
int n, len, sz[M];
ll tag[M], b[M][M], a[N];
void rebuild(int x)
{
    for (int i = (x - 1) * len + 1; i <= min (x * len, n); i++)
        b[x][xth (i)] = a[i];
    sort (b[x] + 1, b[x] + sz[x] + 1);
}
void add(int l, int r, int x)
{
    int tl = l, tr = r;
    bool fl = false, fr = false;
    while (l <= r && xth (l) != 1) a[l++] += x, fl = true;
    while (l + len <= r) tag[pos (l)] += x, l += len;
    while (l <= r) a[l++] += x, fr = true;
    if (fl) rebuild (pos (tl));
    if (fr) rebuild (pos (tr));
}
ll query(int l, int r, ll x)
{
    ll res = -1e18;
    while (l <= r && xth (l) != 1)
    {
        if (a[l] + tag[pos (l)] < x)
            res = max (res, 1ll * a[l] + tag[pos (l)]);
        l++;
    }
    while (l + len <= r)
    {
        int pl = pos (l);
        int p = lower_bound (b[pl] + 1, b[pl] + sz[pl] + 1, x - tag[pl]) - b[pl] - 1;
        if (p) res = max (res, 1ll * b[pl][p] + tag[pl]);
        l += len;
    }
    while (l <= r)
    {
        if (a[l] + tag[pos (l)] < x)
            res = max (res, 1ll * a[l] + tag[pos (l)]);
        l++;
    }
    return (res == -1e18) ? -1 : res;
}
signed main()
{
    cin.tie (0), cout.tie (0);
    ios :: sync_with_stdio (false);
    cin >> n;
    len = sqrt (n);
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
        b[pos (i)][xth (i)] = a[i];
    }
    for (int i = 1; i <= pos (n); i++)
        sz[i] = xth (min (i * len, n)),
        sort (b[i] + 1, b[i] + sz[i] + 1);
    for (int i = 1; i <= n; i++)
    {
        ll op, l, r, x;
        cin >> op >> l >> r >> x;
        if (!op) add (l, r, x);
        else cout << query (l, r, x) << "\n";
    }
    return 0;
}

数列分块入门 4

和 1 类似,区间加区间查,给整块加上懒标记即可。

int n, len;
ll a[N], p[N], sum[M], tag[M], sz[M];
void add(int l, int r, int x)
{
    while (l <= r && l % len != 1)
        sum[p[l]] += x, a[l++] += x;
    while (l + len <= r)
        tag[p[l]] += x, l += len;
    while (l <= r)
        sum[p[l]] += x, a[l++] += x;
}
ll query(int l, int r, ll mod)
{
    ll res = 0;
    while (l <= r && l % len != 1)
        res += a[l] + tag[p[l]], l++;
    while (l + len <= r)
        res += sum[p[l]] + tag[p[l]] * sz[p[l]], l += len;
    while (l <= r)
        res += a[l] + tag[p[l]], l++;
    return (res % mod + mod) % mod;
}
signed main()
{
    cin.tie (0), cout.tie (0);
    ios :: sync_with_stdio (false);
    cin >> n;
    len = sqrt (n);
    for (int i = 1; i <= n; i++)
        cin >> a[i], p[i] = (i - 1) / len + 1,
        sum[p[i]] += a[i], sz[p[i]]++;
    for (int i = 1; i <= n; i++)
    {
        ll op, l, r, x;
        cin >> op >> l >> r >> x;
        if (!op) add (l, r, x);
        else cout << query (l, r, x + 1) << "\n";
        // for (int j = 1; j <= pos (n); j++)
        //     cout << sum[j] << " " << tag[j] << "\n";
        // cout << "\n";
    }
    return 0;
}

数列分块入门 5

和著名的花神游历各国是一个道理,开根的次数有上限,对于整块,记录一下开根对这一块还会不会造成更改(是否有数不是 \(0/1\)),不会就直接跳过。

int n, len;
ll a[N], p[N], sum[M];
bool flag[M];
void rebuild(int x)
{
    if (!flag[x]) return;
    sum[x] = 0, flag[x] = false;
    for (int i = (x - 1) * len + 1; i <= min (x * len, n); i++)
        a[i] = sqrt (a[i]), sum[x] += a[i], flag[x] |= (a[i] > 1);
}
void sqrt_range(int l, int r)
{
    while (l <= r && l % len != 1)
    {
        if (flag[p[l]])
            sum[p[l]] -= a[l], a[l] = sqrt (a[l]), sum[p[l]] += a[l];
        l++;
    }
    while (l + len <= r + 1) rebuild (p[l]), l += len;
    while (l <= r)
    {
        if (flag[p[l]])
            sum[p[l]] -= a[l], a[l] = sqrt (a[l]), sum[p[l]] += a[l];
        l++;
    }
}
ll query(int l, int r)
{
    ll res = 0;
    while (l <= r && l % len != 1)
        res += a[l++];
    while (l + len <= r + 1) res += sum[p[l]], l += len;
    while (l <= r) res += a[l++];
    return res;
}
signed main()
{
    cin.tie (0), cout.tie (0);
    ios :: sync_with_stdio (false);
    cin >> n, len = sqrt (n);
    for (int i = 1; i <= n; i++)
        cin >> a[i], p[i] = (i - 1) / len + 1,
        sum[p[i]] += a[i], flag[p[i]] |= (a[i] > 1);
    for (int i = 1; i <= n; i++)
    {
        int op, l, r;
        cin >> op >> l >> r;
        if (!op) sqrt_range (l, r);
        else cout << query (l, r) << "\n";
    }
    return 0;
}
posted @ 2026-04-25 11:25  wo2011  阅读(8)  评论(0)    收藏  举报