3193 字
16 分钟
竞赛算法(7) 数据结构
并查集
struct UfSet { int n; vector<int> pa, siz;
void init(int _n = 0) { n = _n; pa.assign(_n, 0); iota(pa.begin(), pa.end(), 0); siz.assign(_n, 1); }
UfSet(int _n = 0) { init(_n); }
int find(int x) { while (x != pa[x]) { x = pa[x] = pa[pa[x]]; } return x; }
bool uni(int x, int y) { x = find(x), y = find(y); if (x == y) return false; if (siz[x] < siz[y]) swap(x, y); pa[y] = x; siz[x] += siz[y]; return true; }};树状数组
Fenwick tree 或者叫 Binary Indexed Tree, 功能类似于线段树, 只不过在差分操作的辅助下可以将空间压到, 并且代码简短, 常数小. 树状数组的索引通常从1开始. 树状数组只能维护满足结合律且可差分的数据和运算.
树状数组维护的运算必须满足群的性质.
树状数组和线段树作用的两个区别:
- 如果需要区间查询操作, 树状数组只能维护可差分信息(这一点可以通过维护两个树状数组来解决, 但时间复杂度升高)
- 树状数组修改操作使用的运算必须与维护信息的运算相同(或者说, 构成群结构), 而线段树不需要.(本质区别)
树状数组的性质以下以
c[i]代指i的管辖区间.
- 对于任意索引
x,c[x]为[x - lowbit(x) + 1, x];- 对于任意
x <= y, 要么c[x]和c[y]不相交, 要么c[x]包含于c[y].- 任意
c[x]包含于c[x + lowbit(x)], 并且对于(x, x + lowbit(x))之间任意索引y, 都有c[y]与c[x]不相交.
在了解这些性质之后, 我们可以得到树状数组的单点修改和区间查询操作.
树状数组的区间查询操作以下
f[l, r]代表目标函数从l到r的区间和.
- 要查询
f[l, r]时, 只需查询f[1, r] - f[1, l - 1];- 要查询
f[1, r]时: 初始令i = r, 然后每次让i -= lowbit(i)直到i == 0. 叠加所有的f[c[i]]即可.
树状数组的单点修改操作假如要修改
f[x].
- 修改
f[c[x]].- 令
x = x + lowbit(x), 若x > n说明修改完毕, 否则重复步骤1.
inline int lowbit(int x) { return x & -x; }template<typename S>struct FwkTree { int n; vector<S> d;
void init(int _n) { n = _n; d.assign(_n + 1, 0); } void init(const vector<S>& arr) { init(arr.size()); for (int i = 1; i <= n; i++) { d[i] = arr[i - 1]; } for (int i = 1; i <= n; i++) { int pa = i + lowbit(i); if (pa <= n) d[pa] += d[i]; } } FwkTree(int n = 0) { init(n); } FwkTree(const vector<S>& arr) { init(arr); }
void add(int x, S val) { for (int i = x + 1; i <= n; i += lowbit(i)) { d[i] += val; } }
S sum(int x) { S ret = 0; for (int i = x + 1; i >= 1; i -= lowbit(i)) { ret += d[i]; } return ret; }
S sum(int l, int r) { // [l, r) return sum(r - 1) - sum(l - 1); }};权值树状数组
其实就是把cnt数组存在树状数组里, 起了个高大上的名字.
这时其实我们就能查询大小在区间[L, R]里的数字个数了.
权值树状数组的核心用法: 查询索引在i左边/右边的数字中, 大小在某一区间内的数字个数. 也就是说, 假如数组是a[1], a[2], ..., a[n], 权值树状数组可以在内建树, 在内查询i < q/i > p且a[i] >= L && a[i] <= R的a[i]数量.
有人就要问了, 既然需要的时间建树, 那我为什么不直接暴力查找呢? 这是因为很多题目的查询是离线的, 这时我们可以把所有需要查询的i < q的问题按q的大小排好序, 这样建树的时候只需要从前往后建, 建到q_j的时候查询q_j处的问题, 只需要一次建树就能解决. 分摊复杂度.
SPOJ K-query(说白了就是查询[L, R]处满足>K的数字个数)
// 待补树状数组上二分(倍增)
// 树状数组上二分namespace fwt_ext { // 查找权值树状数组上第k小元素的索引 // 若总数不满k, 返回n // O(log n) template <class fwtree> int kth(const fwtree& fwk, ll k) { int n = fwk.n; assert(n > 0 && k > 0); if (n <= 0 || k <= 0) return -1; int pos = 0; int max_pow = 1 << (31 - __builtin_clz(n)); for (int i = max_pow; i > 0; i >>= 1) { if (pos + i <= n && fwk.d[pos + i] < k) { k -= fwk.d[pos + i]; pos += i; } } return pos; }
// 采用倍增查找满足check(pref[i]) = true的最大下标i. // check 单调谓词函数[T, ..., T, F, ..., F], 返回0-based下的最大下标. // 若check(empty) = false, 返回-1 template <class Monoid, typename F> int search(const FwkTree<Monoid>& fwk, F&& check) { using S = typename Monoid::value_type; int n = fwk.n; assert(n > 0); assert(check(Monoid::e()) == true); int pos = 0; int max_pow = 1 << (31 - __builtin_clz(n)); S cur = Monoid::e(); for (int i = max_pow; i > 0; i >>= 1) { if (pos + i <= n) { S next_val = Monoid::op(cur, fwk.d[pos + i]); if (check(next_val)) { pos += i; cur = next_val; } } } return pos - 1; }}区间加, 区间和
显然, 树状数组可以在的时间内实现单点修改和前缀和查询. 而我们发现如果用这个性质来维护差分数组的话, 单点修改其实就对应了原数组的区间修改, 前缀和就对应了原数组的单点值. 所以用树状数组来维护原数组的差分可以实现区间修改, 单点查询.
ST表
// 稀疏表, Sparse Table.// T为数据类型, op为一个函数指针, 默认产生区间最小值template <typename T, T (*op)(T, T) = std::min>struct SpTable { int n; int max_log; vector<vector<T>> st;
SpTable(const vector<T>& a) : n(a.size()) { if (n == 0) return; max_log = 32 - __builtin_clz(n); st.assign(max_log, vector<T>(n));
for (int i = 0; i < n; i++) st[0][i] = a[i];
for (int j = 1; j < max_log; j++) { for (int i = 0; i + (1 << j) <= n; i++) { st[j][i] = op(st[j - 1][i], st[j - 1][i + (1 << (j - 1))]); } } }
// 查询区间 [l, r) 的结果: O(1) T prod(int l, int r) const { assert(0 <= l && l < r && r <= n); int j = 31 - __builtin_clz(r - l); return op(st[j][l], st[j][r - (1 << j)]); }};哈希表
#include <ext/pb_ds/assoc_container.hpp>#include <ext/pb_ds/tree_policy.hpp>using __gnu_pbds::gp_hash_table;using __gnu_pbds::null_type;/*-------hashtable.hpp-------*/// 防hack哈希表, 基于gp_hash_table, 速度快// 支持整数类型作为键. 其他类型需先转换.// 接口与unordered_set基本一致. 请注意最好不要使用count()struct chash { // 你问我这堆数字是什么, 我也不知道 static uint64_t splitmix64(uint64_t x) { x += 0x9e3779b97f4a7c15; x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9; x = (x ^ (x >> 27)) * 0x94d049bb133111eb; return x ^ (x >> 31); }
size_t operator()(uint64_t x) const { static const uint64_t FIXED_RANDOM = chrono::steady_clock::now().time_since_epoch().count(); return splitmix64(x + FIXED_RANDOM); }};template<typename K, typename V>using HashMap = gp_hash_table<K, V, chash>;template<typename K>using HashSet = gp_hash_table<K, null_type, chash>;笛卡尔树
// 笛卡尔树 (Cartesian Tree), 可以完全代替单调栈// 默认 Compare = less<T> -> 大根堆 (Max-Tree) -> 维护区间最大值template <typename T, typename Compare = std::less<T>>struct CtsTree { int n; int root; vector<int> ltc, rtc; // 左右孩子, 分别代表管辖区间内左右的次大(小)值. vector<int> L, R; // 管辖区间 [L, R] (闭区间)
CtsTree(const vector<T>& a, Compare cmp = Compare()) : n(a.size()) { ltc.assign(n, -1); rtc.assign(n, -1); L.resize(n); R.resize(n);
vector<int> stk; for (int i = 0; i < n; ++i) { L[i] = i; R[i] = i; int last_popped = -1; while (!stk.empty() && cmp(a[stk.back()], a[i])) { last_popped = stk.back(); stk.pop_back(); } if (last_popped != -1) { ltc[i] = last_popped; } if (!stk.empty()) { rtc[stk.back()] = i; } stk.push_back(i); } root = stk.empty() ? -1 : stk[0]; if (root != -1) { init_range(root); // 与建树复杂度一致 } }
// dfs预处理区间, O(n). void init_range(int u) { if (ltc[u] != -1) { init_range(ltc[u]); L[u] = L[ltc[u]]; } if (rtc[u] != -1) { init_range(rtc[u]); R[u] = R[rtc[u]]; } }
// 返回 [l, r) 左闭右开区间 (0-based), 总贡献为((i + 1) - l)(r - i) // 即 [l, i + 1) 和 [i, r) 区间. O(1) pair<int, int> range(int i) const { return {L[i], R[i] + 1}; }};线段树
// 线段树(Segment Tree)// Info 要求包含:// 数据 = 空构造// friend Info operator+(const Info&, const Info&)template<class Info>struct SegTree { int n, size, log; vector<Info> d; void _pull(int x) { d[x] = d[x * 2] + d[x * 2 + 1]; }
// 对外接口 // 重新建树 void build(const vector<Info>& arr) { n = arr.size(); log = 0; size = 1; while (size < n) { size *= 2; log++; } d.assign(2 * size, Info()); for (int i = 0; i < n; i++) { d[size + i] = arr[i]; } for (int i = size - 1; i > 0; i--) { _pull(i); } }
SegTree(const vector<Info>& arr) { build(arr); } SegTree(int _n, const Info& def = Info()) { build(vector<Info>(_n, def)); }
// 单点赋值, O(log n) void set(int x, const Info& val) { assert(0 <= x && x < n); x += size; d[x] = val; // 自底向上更新,无需 push x /= 2; for (; x > 0; x /= 2) { _pull(x); } }
// 区间查询[lt, rt), O(log n) Info query(int lt, int rt) { assert(0 <= lt && lt <= rt && rt <= n); if (lt == rt) { return Info(); } if (lt + 1 == rt) { return d[size + lt]; } lt += size; rt += size; Info sumlt, sumrt; for (; lt < rt; lt /= 2, rt /= 2) { if (lt % 2 == 1) { sumlt = sumlt + d[lt]; lt++; } if (rt % 2 == 1) { rt--; sumrt = d[rt] + sumrt; } } return sumlt + sumrt; }
// 扩展接口 // 查最右rt使[lt, rt)满足check, O(log n) template <class F> int search_right(int lt, F check) { if (lt == n) { return n; } lt += size; Info sum; do { while (lt % 2 == 0) { lt /= 2; } if (!check(sum + d[lt])) { while (lt < size) { lt *= 2; if (check(sum + d[lt])) { sum = sum + d[lt]; lt++; } } return lt - size; } sum = sum + d[lt]; lt++; } while (lowbit(lt) != lt); return n; }
// 查最左lt使得[lt, rt)满足check, O(log n) template <class F> int search_left(int rt, F check) { if (rt == 0) { return 0; } rt += size; Info sum; do { rt--; while (rt > 1 && rt % 2 == 1) { rt /= 2; } if (!check(d[rt] + sum)) { while (rt < size) { rt = 2 * rt + 1; if (check(d[rt] + sum)) { sum = d[rt] + sum; rt--; } } return rt + 1 - size; } sum = d[rt] + sum; } while (lowbit(rt) != rt); return 0; }};lazy tag版
// 懒标记线段树(Lazy Segment Tree)// Info 要求包含:// 节点数据 = 空区间状态(零元)// void apply(const Tag&) 标记作用于数据// friend Info operator+(const Info&, const Info&)// Tag 要求包含:// 标记数据 = 叠加标记不变的状态(单位元)// void apply(const Tag&) 标记叠加template<class Info, class Tag>struct SegTree_lazy { int n, size, log; vector<Info> d; vector<Tag> tag;
#define anc(x) ((x) >> i) #define ck(x) ((anc(x) << i) != x) void _pull(int x) { d[x] = d[x * 2] + d[x * 2 + 1]; } void _apply(int x, const Tag& t) { d[x].apply(t); if (x < size) { tag[x].apply(t); } } void _push(int x) { _apply(x * 2, tag[x]); _apply(x * 2 + 1, tag[x]); tag[x] = Tag(); }
// 对外接口
// 重新建树 void build(const vector<Info>& arr) { n = arr.size(); log = 0; size = 1; while (size < n) { size *= 2; log++; } d.assign(2 * size, Info()); tag.assign(size, Tag()); for (int i = 0; i < n; i++) { d[size + i] = arr[i]; } for (int i = size - 1; i > 0; i--) { _pull(i); } }
SegTree_lazy() : n(0) {} SegTree_lazy(const vector<Info>& arr) { build(arr); } SegTree_lazy(int _n, const Info& def = Info()) { build(vector<Info>(_n, def)); }
// 单点赋值, O(log n) void set(int x, const Info& val) { assert(0 <= x && x < n); x += size; for (int i = log; i > 0; i--) { _push(anc(x)); } d[x] = val; for (int i = 1; i < log + 1; i++) { _pull(anc(x)); } }
// 区间修改[lt, rt), O(log n) void modify(int lt, int rt, const Tag& t) { assert(0 <= lt && lt <= rt && rt <= n); if (lt == rt) { return; } lt += size; rt += size; for (int i = log; i > 0; i--) { if (ck(lt)) { _push(anc(lt)); } if (ck(rt)) { _push(anc(rt - 1)); } } for (int i = lt, j = rt; i < j; i /= 2, j /= 2) { if (i % 2 == 1) { _apply(i, t); i++; } if (j % 2 == 1) { j--; _apply(j, t); } } for (int i = 1; i < log + 1; i++) { if (ck(lt)) { _pull(anc(lt)); } if (ck(rt)) { _pull(anc(rt - 1)); } } }
// 区间查询[lt, rt) Info prod(int lt, int rt) { assert(0 <= lt && lt <= rt && rt <= n); if (lt == rt) { return Info(); } lt += size; rt += size; for (int i = log; i > 0; i--) { if (ck(lt)) { _push(anc(lt)); } if (ck(rt)) { _push(anc(rt - 1)); } } Info sumlt, sumrt; for (; lt < rt; lt /= 2, rt /= 2) { if (lt % 2 == 1) { sumlt = sumlt + d[lt]; lt++; } if (rt % 2 == 1) { rt--; sumrt = d[rt] + sumrt; } } return sumlt + sumrt; }
// 扩展接口 // 查最右rt使[lt, rt)满足check, O(log n) template <class F> int search_right(int lt, F check) { if (lt == n) { return n; } lt += size; for (int i = log; i > 0; i--) { _push(anc(lt)); } Info sum; do { while (lt % 2 == 0) { lt /= 2; } if (!check(sum + d[lt])) { while (lt < size) { _push(lt); lt *= 2; if (check(sum + d[lt])) { sum = sum + d[lt]; lt++; } } return lt - size; } sum = sum + d[lt]; lt++; } while (lowbit(lt) != lt); return n; }
// 查最左lt使得[lt, rt)满足check template <class F> int search_left(int rt, F check) { if (rt == 0) { return 0; } rt += size; for (int i = log; i > 0; i--) { _push(anc(rt - 1)); } Info sum; do { rt--; while (rt > 1 && rt % 2 == 1) { rt /= 2; } if (!check(d[rt] + sum)) { while (rt < size) { _push(rt); rt = 2 * rt + 1; if (check(d[rt] + sum)) { sum = d[rt] + sum; rt--; } } return rt + 1 - size; } sum = d[rt] + sum; } while (lowbit(rt) != rt); return 0; } #undef anc #undef ck}; 竞赛算法(7) 数据结构
https://fuwari.vercel.app/posts/竞赛算法7-数据结构/