详细揭秘:如何发明小波矩阵
Register_int · · 算法·理论
以静态区间第
首先假装你会归并树。归并树是啥?其实就是对归并排序的过程建树。先建立一个线段树,再自底向上归并,求出每个节点对应的区间
归并树可以慢速二维数点。具体地,我们要查区间
究其原因是没有利用好归并树能做二维数点。不妨换维,将下标与值域互换,相当于求:值域在
还能再给力一点吗?注意到换维过后值域是
方便起见把线段树换成
这个大概叫“蝴蝶变换”。方便起见把左子树方向称为红色,右子树方向称为蓝色。
这个树的结构就可以扔掉了,因为我只需要知道一个位置前面有几个蓝色元素,就能直接推出它在蝴蝶变换后会到哪个地方。现在在线段树二分上的过程可以写成这样:
- 从第一层开始。
- 计算
[l,r] 之间的红色元素数量。 - 若个数
\ge k ,则往红色子树走。否则往蓝色子树走。
我们甚至只需要知道元素个数!所以每一层做一个前缀和即可。代码大概长这样:
inline
int kth(int l, int r, int k) {
int res = 0; l--;
for (int i = 29; ~i; i--) {
int ql = b[i][l], qr = b[i][r], c = (r - qr) - (l - ql);
if (c >= k) l -= ql, r -= qr;
else res |= 1 << i, k -= c, l = p[i] + ql, r = p[i] + qr;
}
return res;
}
其中
还能再再再给力一点吗?时间复杂度上很难优化了,但是空间
然后你就吊打主席树了。恭喜你发明了小波矩阵!
struct Bitset {
int n; vector<pair<ull, int>> b;
Bitset() {}
Bitset(vector<ull> a) {
n = (a.size() >> 6) + 1, b.resize(n);
for (int i = 0; i < a.size(); i++) b[i >> 6].first |= a[i] << (i & 63);
for (int i = 1; i < n; i++) b[i].second = b[i - 1].second + __builtin_popcountll(b[i - 1].first);
}
inline int ask(int k) { return b[k >> 6].second + __builtin_popcountll(b[k >> 6].first & (1ull << (k & 63)) - 1); }
};
struct Wavelet {
int n; array<Bitset, 30> b; array<int, 30> p;
Wavelet(vector<int> a) {
n = a.size(); vector<int> t(n);
for (int i = 29; ~i; i--) {
vector<ull> tmp(n); int x = 0, y = 0;
for (int j = 0; j < n; j++) tmp[j] = a[j] >> i & 1;
for (int j = 0; j < n; j++) (a[j] >> i & 1 ? t[y++] : a[x++]) = a[j];
b[i] = Bitset(tmp), p[i] = x;
for (int j = 0; j < y; j++) a[x + j] = t[j];
}
}
inline
int kth(int l, int r, int k) {
int res = 0; l--;
for (int i = 29; ~i; i--) {
int ql = b[i].ask(l), qr = b[i].ask(r), c = (r - qr) - (l - ql);
if (c >= k) l -= ql, r -= qr;
else res |= 1 << i, k -= c, l = p[i] + ql, r = p[i] + qr;
}
return res;
}
};
去掉两个
事实上以这个结构理解,可以使得对小波矩阵进行的拓展更加直观。比如说你可以看出来,小波矩阵本来就是用来做二维数点的,求区间第