树状数组还能干什么?

众所周知,树状数组有常数小,代码短等优点。

本文将进一步开发树状数组不为人知的用法。

树状数组维护不可差分信息

树状数组是 $\log n$ 叉树,$x$ 节点的儿子为 $\{x-2^i|2^i<\operatorname{lowbit}(x)\}$。

所以可以类似线段树地 push up 维护单点修改,区间查询。

// SP1716 GSS3 - Can you answer these queries III
#include <cstdio>
#include <algorithm>
using namespace std;
int n, m;
struct S
{
    int s, l, r, q;
} a[50050], c[50050];
S operator+(S a, S b) { return {a.s + b.s, max(a.l, a.s + b.l), max(b.r, b.s + a.r), max({a.q, b.q, a.r + b.l})}; }
void M(int x, int k)
{
    a[x] = {k, k, k, k};
    for (; x <= n; x += x & -x)
    {
        c[x] = a[x];
        for (int i = 1; i < (x & -x); i <<= 1)
            c[x] = c[x - i] + c[x];
    }
}
int Q(int x, int y)
{
    S q = {0, -1000000000, -1000000000, -1000000000};
    while (y >= x)
    {
        q = a[y--] + q;
        for (; (y & y - 1) >= x; y &= y - 1)
            q = c[y] + q;
    }
    return q.q;
}
int main()
{
    scanf("%d", &n);
    for (int i = 1, x; i <= n; ++i)
        scanf("%d", &x), M(i, x);
    scanf("%d", &m);
    for (int i = 0, o, x, y; i < m; ++i)
    {
        scanf("%d%d%d", &o, &x, &y);
        if (o)
            printf("%d\n", Q(x, y));
        else
            M(x, y);
    }
    return 0;
}

树状数组合并

用哈希表动态开点的树状数组的空间复杂度可以看成 $O(n\log n)$ 的。

所以可以类似线段树合并地维护多棵树状数组,启发式合并哈希表即可。

// P3224 [HNOI2012]永无乡
#include <cstdio>
#include <ext/pb_ds/hash_policy.hpp>
#include <ext/pb_ds/assoc_container.hpp>
using namespace std;
char o;
int n, m, q, a[100050], f[100050];
__gnu_pbds::gp_hash_table<int, int> c[100050];
int F(int x) { return x == f[x] ? x : f[x] = F(f[x]); }
int G(int x, int y) { return c[x].find(y) != c[x].end() ? c[x][y] : 0; }
void M(int x, int y)
{
    int u = F(x), v = F(y);
    if (u == v)
        return;
    if (c[u].size() > c[v].size())
        swap(u, v);
    f[u] = v;
    for (auto [x, y] : c[u])
        c[v][x] += y;
    c[u].clear();
}
int main()
{
    scanf("%d%d", &n, &m);
    a[n + 1] = -1;
    for (int i = 1, x; i <= n; ++i)
    {
        scanf("%d", &x);
        f[i] = a[x] = i;
        for (int k = x; k <= n; k += k & -k)
            ++c[i][k];
    }
    for (int i = 0, u, v; i < m; ++i)
        scanf("%d%d", &u, &v), M(u, v);
    scanf("%d", &q);
    for (int i = 0, x, y, r, s; i < q; ++i)
    {
        scanf(" %c%d%d", &o, &x, &y);
        if (o == 'Q')
        {
            x = F(x);
            r = s = 0;
            for (int k = __lg(n), a, b; k >= 0; --k)
                if ((a = r + (1 << k)) <= n && (b = s + G(x, a)) < y)
                    r = a, s = b;
            printf("%d\n", a[r + 1]);
        }
        else
            M(x, y);
    }
    return 0;
}

P3605、CF208E、P3899 等线段树合并题都可以用树状数组合并做,这里不再赘述。

posted @ 2023-05-12 08:59  5k_sync_closer  阅读(20)  评论(0编辑  收藏  举报  来源