(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
  • 时间复杂度 \(\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\) 子树的哈希值构成的多重集的哈希值。考虑使用 "集合哈希" 来设计哈希函数

\[f_u = \left( c + \sum_{v \in \mathrm{son}(u)} \mathrm{shift}(f_v) \right) \bmod m \]

常数 \(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];
}
posted @ 2022-12-19 10:23  Calculatelove  阅读(500)  评论(0)    收藏  举报