树状数组还能干什么?
众所周知,树状数组有常数小,代码短等优点。
本文将进一步开发树状数组不为人知的用法。
树状数组维护不可差分信息
树状数组是 $\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 等线段树合并题都可以用树状数组合并做,这里不再赘述。