Fenwick in Block Array
简介¶
分块套树状数组在特定条件下可以用来做一些树套树可以做的事情,但是相比起树套树,分块套树状数组代码编写更加简短,更加容易实现。
简单的例子¶
一个简单的例子就是二维平面中矩阵区域内点数的查询。
矩形区域查询
给出
- 给出
a, b, c, d (a, b) c, d - 给出
x, y x y
题目 强制在线,保证
对于操作 1,可以通过矩形容斥将其转化为 4 个二维偏序的查询去解决,然后因为强制在线,CDQ 分治之类的离线算法就解决不了,于是想到了树套树,比如树状数组套 Treap。这确实可以解决这个问题,但是代码太长了,也不是特别好实现。
注意到,题目还额外保证了
初始化¶
首先,一个
然后,以
查询¶
对于操作 1,将其转化为 4 个二维偏序的查询。现在只需要解决给出
现在要查询横坐标的范围为
现在就只需要处理完整的块。暴力扫一遍前面的块,查询每个块对应的树状数组中值小于
这就完事了?不,注意到处理完整的块的时候,其实相当于查询
修改¶
普通的做法就先找到点
如果用了上面说的优化,那就是对
对上述步骤进行一定的改变,比如将一减一加改成只减,就是删点;改成只加,就是加点。但是必须要注意一个
空间复杂度¶
分块分了
时间复杂度¶
查询的话,遍历非完整块的段
修改和查询一样,复杂度为
例题 1¶
Intersection of Permutations
给出两个排列
- 给出
l_a, r_a, l_b, r_b a[l_a ... r_a] b[l_b ... r_b] - 给出
x, y swap(b_x, b_y)
序列长度
对于每个值
参考代码(分块套树状数组-1s)
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5;
const int M = sqrt(N) + 5;
int n, m, pa[N], pb[N];
int nn, block_size, block_cnt, block_id[N], L[N], R[N], T[M][N];
void build(int n) {
nn = n;
block_size = sqrt(nn);
block_cnt = nn / block_size;
for (int i = 1; i <= block_cnt; ++i) {
L[i] = R[i - 1] + 1;
R[i] = i * block_size;
}
if (R[block_cnt] < nn) {
++block_cnt;
L[block_cnt] = R[block_cnt - 1] + 1;
R[block_cnt] = nn;
}
for (int j = 1; j <= block_cnt; ++j)
for (int i = L[j]; i <= R[j]; ++i) block_id[i] = j;
}
inline int lb(int x) { return x & -x; }
void add(int p, int v, int d) {
for (int i = block_id[p]; i <= block_cnt; i += lb(i))
for (int j = v; j <= nn; j += lb(j)) T[i][j] += d;
}
int getsum(int p, int v) {
if (!p) return 0;
int res = 0;
int id = block_id[p];
for (int i = L[id]; i <= p; ++i)
if (pb[i] <= v) ++res;
for (int i = id - 1; i; i -= lb(i))
for (int j = v; j; j -= lb(j)) res += T[i][j];
return res;
}
void update(int x, int y) {
add(x, pb[x], -1);
add(y, pb[y], -1);
swap(pb[x], pb[y]);
add(x, pb[x], 1);
add(y, pb[y], 1);
}
int query(int la, int ra, int lb, int rb) {
int res = getsum(rb, ra) - getsum(rb, la - 1) - getsum(lb - 1, ra) +
getsum(lb - 1, la - 1);
return res;
}
int main() {
scanf("%d %d", &n, &m);
int v;
for (int i = 1; i <= n; ++i) scanf("%d", &v), pa[v] = i;
for (int i = 1; i <= n; ++i) scanf("%d", &v), pb[i] = pa[v];
build(n);
for (int i = 1; i <= n; ++i) add(i, pb[i], 1);
int op, la, lb, ra, rb, x, y;
for (int i = 1; i <= m; ++i) {
scanf("%d", &op);
if (op == 1) {
scanf("%d %d %d %d", &la, &ra, &lb, &rb);
printf("%d\n", query(la, ra, lb, rb));
} else if (op == 2) {
scanf("%d %d", &x, &y);
update(x, y);
}
}
return 0;
}
参考代码(树状数组套Treap-TLE)
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5;
mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());
int n, m, pa[N], pb[N];
// Treap
struct Treap {
struct node {
node *l, *r;
int sz, rnd, v;
node(int _v) : l(NULL), r(NULL), sz(1), rnd(rng()), v(_v) {}
};
inline int get_size(node*& p) { return p ? p->sz : 0; }
inline void push_up(node*& p) {
if (!p) return;
p->sz = get_size(p->l) + get_size(p->r) + 1;
}
node* root;
node* merge(node* a, node* b) {
if (!a) return b;
if (!b) return a;
if (a->rnd < b->rnd) {
a->r = merge(a->r, b);
push_up(a);
return a;
} else {
b->l = merge(a, b->l);
push_up(b);
return b;
}
}
void split_val(node* p, const int& k, node*& a, node*& b) {
if (!p)
a = b = NULL;
else {
if (p->v <= k) {
a = p;
split_val(p->r, k, a->r, b);
push_up(a);
} else {
b = p;
split_val(p->l, k, a, b->l);
push_up(b);
}
}
}
void split_size(node* p, int k, node*& a, node*& b) {
if (!p)
a = b = NULL;
else {
if (get_size(p->l) <= k) {
a = p;
split_size(p->r, k - get_size(p->l), a->r, b);
push_up(a);
} else {
b = p;
split_size(p->l, k, a, b->l);
push_up(b);
}
}
}
void ins(int val) {
node *a, *b;
split_val(root, val, a, b);
a = merge(a, new node(val));
root = merge(a, b);
}
void del(int val) {
node *a, *b, *c, *d;
split_val(root, val, a, b);
split_val(a, val - 1, c, d);
delete d;
root = merge(c, b);
}
int qry(int val) {
node *a, *b;
split_val(root, val, a, b);
int res = get_size(a);
root = merge(a, b);
return res;
}
int qry(int l, int r) { return qry(r) - qry(l - 1); }
};
// Fenwick Tree
Treap T[N];
inline int lb(int x) { return x & -x; }
void ins(int x, int v) {
for (; x <= n; x += lb(x)) T[x].ins(v);
}
void del(int x, int v) {
for (; x <= n; x += lb(x)) T[x].del(v);
}
int qry(int x, int mi, int ma) {
int res = 0;
for (; x; x -= lb(x)) res += T[x].qry(mi, ma);
return res;
}
int main() {
scanf("%d %d", &n, &m);
int v;
for (int i = 1; i <= n; ++i) scanf("%d", &v), pa[v] = i;
for (int i = 1; i <= n; ++i) scanf("%d", &v), pb[i] = pa[v];
for (int i = 1; i <= n; ++i) ins(i, pb[i]);
int op, la, lb, ra, rb, x, y;
for (int i = 1; i <= m; ++i) {
scanf("%d", &op);
if (op == 1) {
scanf("%d %d %d %d", &la, &ra, &lb, &rb);
printf("%d\n", qry(rb, la, ra) - qry(lb - 1, la, ra));
} else if (op == 2) {
scanf("%d %d", &x, &y);
del(x, pb[x]);
del(y, pb[y]);
swap(pb[x], pb[y]);
ins(x, pb[x]);
ins(y, pb[y]);
}
}
return 0;
}
例题 2¶
Complicated Computations
给出一个序列
序列的长度
观察:一个序列的 MEX 为
依次判断是否存在 MEX 为
在判断
用一个数组
如果在判断完值为
参考代码(分块套树状数组-78ms)
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
const int M = sqrt(N) + 5;
// 分块
int nn, b[N], block_size, block_cnt, block_id[N], L[N], R[N], T[M][N];
void build(int n) {
nn = n;
block_size = sqrt(nn);
block_cnt = nn / block_size;
for (int i = 1; i <= block_cnt; ++i) {
L[i] = R[i - 1] + 1;
R[i] = i * block_size;
}
if (R[block_cnt] < nn) {
++block_cnt;
L[block_cnt] = R[block_cnt - 1] + 1;
R[block_cnt] = nn;
}
for (int j = 1; j <= block_cnt; ++j)
for (int i = L[j]; i <= R[j]; ++i) block_id[i] = j;
}
inline int lb(int x) { return x & -x; }
// d = 1: 加点(p, v)
// d = -1: 删点(p, v)
void add(int p, int v, int d) {
for (int i = block_id[p]; i <= block_cnt; i += lb(i))
for (int j = v; j <= nn; j += lb(j)) T[i][j] += d;
}
// 询问[1, r]内,纵坐标小于等于val的点有多少个
int getsum(int p, int v) {
if (!p) return 0;
int res = 0;
int id = block_id[p];
for (int i = L[id]; i <= p; ++i)
if (b[i] && b[i] <= v) ++res;
for (int i = id - 1; i; i -= lb(i))
for (int j = v; j; j -= lb(j)) res += T[i][j];
return res;
}
// 询问[l, r]内,纵坐标小于等于val的点有多少个
int query(int l, int r, int val) {
if (l > r) return -1;
int res = getsum(r, val) - getsum(l - 1, val);
return res;
}
// 加点(p, v)
void update(int p, int v) {
b[p] = v;
add(p, v, 1);
}
int n, a[N];
vector<int> g[N];
int main() {
scanf("%d", &n);
// 为了减少讨论,加了哨兵节点
// 因为树状数组添加的时候,为0可能会死循环,所以整体往右偏移一位
// a_1和a_{n+2}为哨兵节点
for (int i = 2; i <= n + 1; ++i) scanf("%d", &a[i]);
for (int i = 2; i <= n + 1; ++i) g[a[i]].push_back(i);
// 分块
build(n + 2);
int ans = n + 2, lst, ok;
for (int i = 1; i <= n + 1; ++i) {
g[i].push_back(n + 2);
lst = 1;
ok = 0;
for (int pos : g[i]) {
if (query(lst + 1, pos - 1, lst) == i - 1) {
ok = 1;
break;
}
lst = pos;
}
if (!ok) {
ans = i;
break;
}
lst = 1;
g[i].pop_back();
for (int pos : g[i]) {
update(pos, lst);
lst = pos;
}
}
printf("%d\n", ans);
return 0;
}
参考代码(线段树套Treap-468ms)
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
vector<int> g[N];
int n, a[N];
mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());
struct Treap {
struct node {
node *l, *r;
unsigned rnd;
int sz, v;
node(int _v) : l(NULL), r(NULL), rnd(rng()), sz(1), v(_v) {}
};
inline int get_size(node*& p) { return p ? p->sz : 0; }
inline void push_up(node*& p) {
if (!p) return;
p->sz = get_size(p->l) + get_size(p->r) + 1;
}
node* root;
node* merge(node* a, node* b) {
if (!a) return b;
if (!b) return a;
if (a->rnd < b->rnd) {
a->r = merge(a->r, b);
push_up(a);
return a;
} else {
b->l = merge(a, b->l);
push_up(b);
return b;
}
}
void split_val(node* p, const int& k, node*& a, node*& b) {
if (!p)
a = b = NULL;
else {
if (p->v <= k) {
a = p;
split_val(p->r, k, a->r, b);
push_up(a);
} else {
b = p;
split_val(p->l, k, a, b->l);
push_up(b);
}
}
}
void split_size(node* p, int k, node*& a, node*& b) {
if (!p)
a = b = NULL;
else {
if (get_size(p->l) <= k) {
a = p;
split_size(p->r, k - get_size(p->l), a->r, b);
push_up(a);
} else {
b = p;
split_size(p->l, k, a, b->l);
push_up(b);
}
}
}
void insert(int val) {
node *a, *b;
split_val(root, val, a, b);
a = merge(a, new node(val));
root = merge(a, b);
}
int query(int val) {
node *a, *b;
split_val(root, val, a, b);
int res = get_size(a);
root = merge(a, b);
return res;
}
int qry(int l, int r) { return query(r) - query(l - 1); }
};
// Segment Tree
Treap T[N << 2];
void insert(int x, int l, int r, int p, int val) {
T[x].insert(val);
if (l == r) return;
int mid = (l + r) >> 1;
if (p <= mid)
insert(x << 1, l, mid, p, val);
else
insert(x << 1 | 1, mid + 1, r, p, val);
}
int query(int x, int l, int r, int L, int R, int val) {
if (l == L && r == R) return T[x].query(val);
int mid = (l + r) >> 1;
if (R <= mid) return query(x << 1, l, mid, L, R, val);
if (L > mid) return query(x << 1 | 1, mid + 1, r, L, R, val);
return query(x << 1, l, mid, L, mid, val) +
query(x << 1 | 1, mid + 1, r, mid + 1, R, val);
}
int query(int l, int r, int val) {
if (l > r) return -1;
return query(1, 1, n, l, r, val);
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
for (int i = 1; i <= n; ++i) g[a[i]].push_back(i);
// a_0 和 a_{n+1}为哨兵节点
int ans = n + 2, lst, ok;
for (int i = 1; i <= n + 1; ++i) {
g[i].push_back(n + 1);
lst = 0;
ok = 0;
for (int pos : g[i]) {
if (query(lst + 1, pos - 1, lst) == i - 1) {
ok = 1;
break;
}
lst = pos;
}
if (!ok) {
ans = i;
break;
}
lst = 0;
g[i].pop_back();
for (int pos : g[i]) {
insert(1, 1, n, pos, lst);
lst = pos;
}
}
printf("%d\n", ans);
return 0;
}
buildLast update and/or translate time of this article,Check the history
editFound smelly bugs? Translation outdated? Wanna contribute with us? Edit this Page on Github
peopleContributor of this article OI-wiki
translateTranslator of this article Visit the original article!
copyrightThe article is available under CC BY-SA 4.0 & SATA ; additional terms may apply.