(v4 更新)0x40 数据结构
0x41 数据结构 序列结构
树状数组
0x41 树状数组.cpp:
// 树状数组
namespace BIT {
int c[N];
void add(int x, int y) {
for (; x <= n; x += x & -x) {
c[x] += y;
}
}
int ask(int x) {
int ans = 0;
for (; x; x -= x & -x) {
ans += c[x];
}
return ans;
}
}
线段树
权值线段树
神功早已大成!日后补全功法。
主席树
神功早已大成!日后补全功法。
线段树上二分
在线段树上二分时,你应该确保每当遍历到一个 "被询问区间完全覆盖的节点" 时,就可以判断该区间是否存在答案。
势能线段树
区间开平方:一个整数 \(x\) 开平方 \(\mathcal{O}(\log \log n)\) 次之后就会变成 \(1\)。若区间 \(\mathrm{max} = 1\) 则 return。
区间取模:一个整数 \(x(x \ge p)\) 对 \(p\) 取模,\(x\) 至少变小一半。若区间 \(\mathrm{max} < p\) 则 return。
区间除法:整除 \(p\) 之后,区间最大值与最小值的差至少变小一半。
- 若区间 \(\mathrm{max} - \mathrm{min} = 0\),则区间加之后
return。 - 若区间 \(\mathrm{max} - \mathrm{min} = 1\) 且 \(\lfloor \frac{\mathrm{\max}}{p} \rfloor \neq \lfloor \frac{\mathrm{\min}}{p} \rfloor\),则区间加之后
return。
区间按位与:一个二进制数 \(x\) 与 \(v\) 进行按位与,至多进行 \(\log v\) 次有效操作。若区间(在二进制下)\(\mathrm{bitor} \subseteq x\) 则 return。
区间取最值(吉老师线段树):以区间取最小值为例:
- 在线段树上的每一个节点 \(p\) 需要维护:
mx:区间最大值。cnt:区间最大值个数。md:区间严格次大值。
- 在区间修改时,若遇到一个 "被询问区间完全覆盖的节点" 时:
- 若 \(x \geq \mathrm{mx}\),则
return。 - 若 \(x \leq \mathrm{md}\),则暴力递归左右儿子修改。
- 若 \(\mathrm{md} < x < \mathrm{mx}\),则有 \(\mathrm{cnt}\) 个 \(\mathrm{mx}\) 被修改成 \(x\),打标记后
return。
- 若 \(x \geq \mathrm{mx}\),则
- 时间复杂度 \(\mathcal{O}(n \log^2 n)\)。
0x41 吉老师线段树.cpp:
// 吉老师线段树
namespace SGT {
struct node {
int len;
i64 sum;
int mx, md;
int cnt;
int add; // 加法标记(初始值为 0)
int tag; // 区间取 min 标记(初始值为 +inf,需要根据数据范围更改)
void mk_add(int x) {
sum += 1ll * x * len;
mx += x, md += x;
add += x;
if (tag < inf) tag += x;
}
void mk_tag(int x) {
if (mx <= x) return;
sum -= 1ll * (mx - x) * cnt;
mx = x;
tag = x;
}
} t[N * 4];
void upd(int p) {
t[p].len = t[p * 2].len + t[p * 2 + 1].len;
t[p].sum = t[p * 2].sum + t[p * 2 + 1].sum;
if (t[p * 2].mx == t[p * 2 + 1].mx) {
t[p].mx = t[p * 2].mx, t[p].cnt = t[p * 2].cnt + t[p * 2 + 1].cnt;
t[p].md = std::max(t[p * 2].md, t[p * 2 + 1].md);
} else if (t[p * 2].mx > t[p * 2 + 1].mx) {
t[p].mx = t[p * 2].mx, t[p].cnt = t[p * 2].cnt;
t[p].md = std::max(t[p * 2].md, t[p * 2 + 1].mx);
} else {
t[p].mx = t[p * 2 + 1].mx, t[p].cnt = t[p * 2 + 1].cnt;
t[p].md = std::max(t[p * 2 + 1].md, t[p * 2].mx);
}
}
void spread(int p) {
if (t[p].add) {
t[p * 2].mk_add(t[p].add), t[p * 2 + 1].mk_add(t[p].add);
t[p].add = 0;
}
if (t[p].tag < inf) {
t[p * 2].mk_tag(t[p].tag), t[p * 2 + 1].mk_tag(t[p].tag);
t[p].tag = inf;
}
}
void build(int p, int l, int r) {
t[p].add = 0, t[p].tag = inf;
if (l == r) {
t[p].len = 1, t[p].sum = a[l];
t[p].mx = a[l], t[p].md = -inf;
t[p].cnt = 1;
return;
}
int mid = (l + r) >> 1;
build(p * 2, l, mid), build(p * 2 + 1, mid + 1, r);
upd(p);
}
void change_add(int p, int l, int r, int s, int e, int x) {
if (s <= l && r <= e) {
t[p].mk_add(x);
return;
}
spread(p);
int mid = (l + r) >> 1;
if (s <= mid) {
change_add(p * 2, l, mid, s, e, x);
}
if (mid < e) {
change_add(p * 2 + 1, mid + 1, r, s, e, x);
}
upd(p);
}
void change_min(int p, int l, int r, int s, int e, int x) {
if (s <= l && r <= e && t[p].md < x) {
t[p].mk_tag(x);
return;
}
spread(p);
int mid = (l + r) >> 1;
if (s <= mid) {
change_min(p * 2, l, mid, s, e, x);
}
if (mid < e) {
change_min(p * 2 + 1, mid + 1, r, s, e, x);
}
upd(p);
}
}
历史线段树
李超线段树
0x42 数据结构 树上结构
最近公共祖先
0x42 欧拉序求 LCA.cpp:
int dep[N];
int Fir[N];
std::vector<int> eul;
void dfs_init(int u, int fu) {
dep[u] = dep[fu] + 1;
Fir[u] = eul.size(), eul.push_back(u);
for (int v : G[u]) {
if (v == fu) {
continue;
}
dfs_init(v, u);
eul.push_back(u);
}
}
namespace eulST {
int n, t;
std::vector<std::vector<int>> f;
inline int op(int x, int y) {
return dep[x] < dep[y] ? x : y;
}
void init() {
n = eul.size(), t = std::__lg(n);
f.assign(t + 1, std::vector<int>(n, 0));
for (int i = 0; i < n; i ++) {
f[0][i] = eul[i];
}
for (int j = 1; j <= t; j ++) {
for (int i = 0; i + (1 << j) - 1 < n; i ++) {
f[j][i] = op(f[j - 1][i], f[j - 1][i + (1 << (j - 1))]);
}
}
}
int ask(int l, int r) {
int k = std::__lg(r - l + 1);
return op(f[k][l], f[k][r - (1 << k) + 1]);
}
}
int lca(int x, int y) {
auto [l, r] = std::minmax(Fir[x], Fir[y]);
return eulST::ask(l, r);
}
树的同构、树哈希
树的同构:\(T_1, T_2\) 为同构树,当且仅当存在一个 \(1 \sim n\) 的排列 \(f\),满足 \((i, j) \in E \Leftrightarrow (f_i, f_j) \in E\)。
有根树哈希:设 \(f_u\) 表示以 \(u\) 为根的子树的哈希值,则 \(f_u\) 等于 \(u\) 的所有儿子 \(v\) 子树的哈希值构成的多重集的哈希值。考虑使用 "集合哈希" 来设计哈希函数
常数 \(c\) 选择 \(1\),映射函数使用 \(\mathrm{xorshift}\),取模使用 unsigned long long 的自然溢出。
这样设计的哈希函数可以支持换根。
0x42 有根树哈希.cpp:
u64 mask = std::mt19937_64(std::random_device{}())();
u64 xorshift(u64 x) {
x ^= mask;
x ^= x << 13, x ^= x >> 7, x ^= x << 17;
x ^= mask;
return x;
}
u64 f[N];
void dfs1_hash(int u, int fu) {
f[u] = 1;
for (int v : G[u]) {
if (v == fu) {
continue;
}
dfs1_hash(v, u);
f[u] += xorshift(f[v]);
}
}
u64 g[N];
void dfs2_hash(int u, int fu) {
if (!fu) {
g[u] = f[u];
}
for (int v : G[u]) {
if (v == fu) {
continue;
}
g[v] = f[v] + xorshift(g[u] - xorshift(f[v]));
dfs2_hash(v, u);
}
}
无根树哈希:
方法 1:换根 dp 求出以每个点为根时整棵树的哈希值,然后对这 \(n\) 个哈希值做一次 "集合哈希" 作为无根时整棵树的哈希值。
方法 2:找一个代表节点。当重心唯一时,取重心为根;当重心不唯一时,在重心之间加一点,取该点为根。转化成有根树哈希。
有根树自同构树计数:对于所有点,将其每个儿子的子树按照树同构划分出等价类(使用树哈希判断),根据每种等价类大小的阶乘乘入答案。
无根树自同构树计数:找一个代表节点。当重心唯一时,取重心为根;当重心不唯一时,在重心之间加一点,取该点为根。转化为有根树自同构树计数。
树链求交
0x42 树链求交.cpp:
// 树链
struct path {
int x, y;
path() {}
path(int _x, int _y) : x(_x), y(_y) {}
// 树链求交
path operator & (const path &rhs) const {
if (!x || !rhs.x) return path(0, 0);
int d[4], c[2];
d[0] = lca(x, rhs.x), d[1] = lca(x, rhs.y);
d[2] = lca(y, rhs.x), d[3] = lca(y, rhs.y);
c[0] = lca(x, y), c[1] = lca(rhs.x, rhs.y);
auto cmp = [] (int x, int y) {
return dep[x] < dep[y];
};
std::sort(d, d + 4, cmp), std::sort(c, c + 2, cmp);
if (dep[c[0]] <= dep[d[0]] && dep[c[1]] <= dep[d[2]]) {
return path(d[2], d[3]);
} else {
return path(0, 0);
}
}
};
重链剖分
0x42 重链剖分.cpp:
int dep[N], sze[N];
int Fa[N], son[N];
void dfs1_init(int u, int fu) {
dep[u] = dep[fu] + 1;
sze[u] = 1;
Fa[u] = fu, son[u] = 0;
for (int v : G[u]) {
if (v == fu) {
continue;
}
dfs1_init(v, u);
sze[u] += sze[v];
if (sze[v] > sze[son[u]]) {
son[u] = v;
}
}
}
int dfsClock, dfn[N], idx[N];
int Top[N];
void dfs2_init(int u, int P) {
dfsClock ++;
dfn[u] = dfsClock, idx[dfsClock] = u;
Top[u] = P;
if (son[u]) {
dfs2_init(son[u], P);
}
for (int v : G[u]) {
if (v == Fa[u] || v == son[u]) {
continue;
}
dfs2_init(v, v);
}
}
void path_change(int x, int y) {
while (Top[x] ^ Top[y]) {
if (dep[Top[x]] > dep[Top[y]]) std::swap(x, y);
// 在 dfs 序上的区间 [dfn[Top[y]], dfn[y]] 进行操作
y = Fa[Top[y]];
}
if (dep[x] > dep[y]) std::swap(x, y);
// 在 dfs 序上的区间 [dfn[x], dfn[y]] 进行操作
}
长链剖分
0x42 长链剖分求 k 级祖先.cpp:
int dep[N], mdep[N];
int Fa[N], son[N], anc[20][N]; // 最大指数 log n 需要根据实际问题调整
void dfs1_init(int u, int fu) {
mdep[u] = dep[u] = dep[fu] + 1;
Fa[u] = fu, son[u] = 0;
anc[0][u] = fu;
for (int i = 1; i <= 19; i ++) { // 最大指数 log n 需要根据实际问题调整
anc[i][u] = anc[i - 1][anc[i - 1][u]];
}
for (int v : G[u]) {
if (v == fu) {
continue;
}
dfs1_init(v, u);
if (mdep[u] < mdep[v]) {
mdep[u] = mdep[v], son[u] = v;
}
}
}
int Top[N];
std::vector<int> up[N], dn[N];
void dfs2_init(int u, int P) {
Top[u] = P;
if (u == P) {
int l = mdep[u] - dep[u] + 1;
for (int i = 1, x = u; i <= l; i ++, x = Fa[x]) {
up[u].push_back(x);
}
for (int i = 1, x = u; i <= l; i ++, x = son[x]) {
dn[u].push_back(x);
}
}
if (son[u]) {
dfs2_init(son[u], P);
}
for (int v : G[u]) {
if (v == Fa[u] || v == son[u]) {
continue;
}
dfs2_init(v, v);
}
}
int jump(int x, int k) {
if (!k) return x;
int h = std::__lg(k);
k -= (1 << h), x = anc[h][x];
k -= dep[x] - dep[Top[x]], x = Top[x];
return k >= 0 ? up[x][k] : dn[x][-k];
}

浙公网安备 33010602011771号