ACM 数据结构与算法思想记录

老年 ACMer 尝试对抗阿尔茨海默病(

图论

DFS序 \(O(n \log n)\) - \(O(1)\) Lca

考虑点\(u\)\(v\) 及其 \(Lca\)\(l\),不妨设 $dfn_u \lt dfn_v $,那么有 \(dfs\) 序从 \(l\)\(u\) 递增,此后回到 \(l\) 后再向着 \(v\)递增

所以,按\(dfs\)序构成的序列中,\(dfn_u\)\(dfn_v\)这一段中深度最小的点一定是\(l\)\(v\)方向直接连接的节点

所以可以直接使用\(ST\)表维护父节点\(dfs\)序的最小值即可

注意可能有\(u\)\(v\)的祖先的情况,因此\(ST\)表询问区间该为\([dfn_u+1,dfn_v]\)进行规避

P3379 【模板】最近公共祖先(LCA)

点击查看代码
int mindfn(int x, int y) {
    if (dfn[x] < dfn[y]) return x;
    return y;
}

struct ST {
    int st[MAXN][25];
    void init(int n) {
        int t = std::__lg(n) + 1;
        for (int i = 1; i <= n; i++) st[dfn[i]][0] = fa[i];
        for (int j = 1; j <= t; j++) {
            for (int i = 1; i  + (1 << (j - 1)) <= n; i++) {
                st[i][j] = mindfn(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
            }
        }
    }
    int query(int l, int r) {
        int t = std::__lg(r - l + 1);
        return mindfn(st[l][t], st[r - (1 << t) + 1][t]);
    }
}st;

auto Lca = [&](int x, int y) {
    if (dfn[x] > dfn[y]) std::swap(x, y);
    return st.query(dfn[x] + 1, dfn[y]);   
};

Dsu on tree

当涉及子树问题时,对于父亲相同的子树,如果在计算一个的答案时保留了其他子树的信息,统计会出现混乱。但是这种保留对于父亲子树的统计有利

于是考虑对于每个子树,先计算它轻儿子子树中的答案,并且不保留。最后计算重儿子的答案并保留,将轻儿子的答案往上合并

对于每个节点,每当其到根节点有一条轻链时会被暴力合并一次,由树链剖分的性质可知最多会被合并 \(O(\log n)\)

CF600E Lomsat gelral

点击查看代码
#include <bits/stdc++.h>

#define ll long long

const int MAXN = 1e5 + 5;

int n, a[MAXN], fa[MAXN], siz[MAXN], son[MAXN];
std::vector <int> G[MAXN];
ll ans[MAXN];
int buc[MAXN], mx, S;
ll sum;

int main() {
    std::ios::sync_with_stdio(0);
    std::cin.tie(0);
    std::cout.tie(0);

    int n;
    std::cin >> n;
    for (int i = 1; i <= n; i++) std::cin >> a[i];
    for (int i = 1;  i < n; i++) {
        int u, v;
        std::cin >> u >> v;
        G[u].push_back(v);
        G[v].push_back(u);
    }

    auto dfs1 = [&](int u, int f, auto self) -> void {
        fa[u] = f; siz[u] = 1;
        for (const auto &v : G[u]) {
            if (v == f) continue;
            self(v, u, self);
            siz[u] += siz[v];
            if (siz[v] > siz[son[u]]) son[u] = v;
        }
    };
    dfs1(1, 0, dfs1);

    auto calc = [&](int u, int delta, auto self) -> void {
        buc[a[u]] += delta;
        if (buc[a[u]] > mx) {
            mx = buc[a[u]];
            sum = a[u];
        }
        else if (buc[a[u]] == mx) {
            sum += a[u];
        }
        for (const auto &v : G[u]) {
            if (v == fa[u] || v == S) continue;
            self(v, delta, self);
        }
    };

    auto dfs2 = [&](int u, bool keep, auto self) -> void {
        for (const auto &v : G[u]) {
            if (v == fa[u] || v == son[u]) continue;
            self(v, 0, self);
        }
        if (son[u]) {
            self(son[u], 1, self);
            S = son[u];
        }
        calc(u, 1, calc);
        ans[u] = sum;
        S = 0;
        if (!keep) {
            calc(u, -1, calc);
            mx = 0; sum = 0;
        }
    };

    dfs2(1, 1, dfs2);
    for (int i = 1; i <= n; i++) {
        std::cout << ans[i] << " "; 
    }
    std::cout << '\n';
}

点分治

对于树上的路径问题,我们考虑固定一个根的情况,答案分为两种:跨过根的路径(包括以根为端点)和根的子树内部的路径

显然后者有子问题结构,考虑分治。当每次的根都取子树重心时,有最少递归层数 \(O(\log n)\)

由于较小的递归层数,我们可以每一层都采取相对暴力的做法来统计跨过根的路径

注意每次向下分治时重新计算子树大小,不然会导致重心求解出错,时间复杂度假掉

P3806 【模板】点分治

点击查看代码
#include <bits/stdc++.h>

const int MAXN = 1e4 + 5;

std::vector <std::pair <int, int> > G[MAXN];
int qry[105];
bool ans[105];
std::bitset <10000005> exist;

int main() {
    int n, q;
    std::cin >> n >> q;
    for (int i = 1; i < n; i++) {
        int u, v, w;
        std::cin >> u >> v >> w;
        G[u].push_back({v, w});
        G[v].push_back({u, w});
    }
    for (int i = 1; i <= q; i++) {
        std::cin >> qry[i];
    }

    std::vector <bool> vis(n + 1);
    std::vector <int> mx(n + 1), siz(n + 1);
    int rt;
    int node_cnt;
    auto Find_size = [&](int u, int f, auto self) -> void {
        siz[u] = 1;
        for (const auto &p : G[u]) {
            int v = p.first, w = p.second;
            if (v == f || vis[v]) continue;
            self(v, u, self);
            siz[u] += siz[v];
        }
    };
    auto Find_cent = [&](int u, int f, auto self) -> void {
        mx[u] = 0;
        for (const auto &p : G[u]) {
            int v = p.first, w = p.second;
            if (vis[v] || v == f) continue;
            self(v, u, self);
            mx[u] = std::max(mx[u], siz[v]);
        }
        mx[u] = std::max(mx[u], node_cnt - siz[u]);
        if (mx[u] < mx[rt]) rt = u;
    };
    std::vector <int> dis;
    auto Find_dis = [&](int u, int f, int d, auto self) -> void {
        if (d > 10000000) return;
        dis.push_back(d);
        for (const auto & p : G[u]) {
            int v = p.first, w = p.second;
            if (v == f || vis[v]) continue;
            self(v, u, d + w, self);
        }
    };
    std::vector <int> used;
    auto calc = [&](int u, int d, auto self) -> void {
        Find_dis(u, u, d, Find_dis);
        for (const auto &x : dis) {
            for (int i = 1; i <= q; i++) {
                if (qry[i] - x >= 0) ans[i] |= exist[qry[i] - x];
            }
        }
        for (const auto &x : dis) {
            exist[x] = 1;
            used.push_back(x);
        }
        dis.clear();
    };
    auto solve = [&](int u, auto self) -> void {
        vis[u] = 1; exist[0] = 1;
        for (const auto &p : G[u]) {
            int v = p.first, w = p.second;
            if (vis[v]) continue;
            calc(v, w, calc);
        } 
        for (const auto &x : used) exist[x] = 0;
        used.clear();
        for (const auto &p : G[u]) {
            int v = p.first;
            if (vis[v]) continue;
            mx[rt = 0] = 0x3f3f3f3f;
            Find_size(v, u, Find_size);
            node_cnt = siz[v];
            Find_cent(v, u, Find_cent);
            self(rt, self);
        }
    };

    mx[rt = 0] = 0x3f3f3f3f;
    node_cnt = n;
    Find_size(1, 0, Find_size);
    Find_cent(1, 0, Find_cent);
    solve(rt, solve);

    for (int i = 1; i <= q; i++) {
        if (ans[i]) {
            std::cout << "AYE" << '\n';
        }
        else std::cout << "NAY" << '\n';
    }
}

点分树

考虑利用点分治只会递归\(\log n\)层的优秀性质,每次递归的时候,子树的重心向当前根节点连边,就形成了一颗\(\log n\)的树

这棵树满足对于\(l = Lca(u, v)\),必有 \(l\) 在原树 \(u\)\(v\)的路径上

因此可以从在点分树上从\(u\)开始往上跳,假设当前跳到了\(l\),那么对\(l\)除去\(u\)方向的子树的所有节点,都有一条在原树中经过\(l\)到达\(u\)的路径,可以采用数据结构进行统计

树高的性质保证了最多向上跳 \(\log n\) 次,时间复杂度正确

P6329 【模板】点分树 / 震波

点击查看代码
#include <bits/stdc++.h>

const int MAXN = 1e5 + 5;

int n, m, a[MAXN];
std::vector <int> G[MAXN];

struct Sgt {
    struct Node {
        int ls, rs, sum;
    }t[MAXN * 80];
    int cnt;
    void update(int &p, int l, int r, int pos, int val) {
        if (!p) p = ++cnt;
        t[p].sum += val;
        if (l == r) return;
        int mid = (l + r) >> 1;
        if (pos <= mid) update(t[p].ls, l, mid, pos, val);
        else update(t[p].rs, mid + 1, r, pos, val);
    }
    int query(int p, int l, int r, int ql, int qr) {
        if (!p || ql > qr) return 0;
        if (ql <= l && r <= qr) return t[p].sum;
        int mid = (l + r) >> 1, ret = 0;
        if (ql <= mid) ret += query(t[p].ls, l, mid, ql, qr);
        if (qr > mid) ret += query(t[p].rs, mid + 1, r, ql, qr);
        return ret;
    }
}t1, t2;
int rt1[MAXN], rt2[MAXN];

int lst;
int dfn[MAXN], fa[MAXN], dcnt, dep[MAXN];

void dfs(int u, int f) {
    fa[u] = f; dfn[u] = ++dcnt; dep[u] = dep[f] + 1;
    for (const auto &v : G[u]) {
        if (v == f) continue;
        dfs(v, u);
    }
}

struct ST {
    int st[MAXN][25];
    int dfnmin(int x, int y) {
        if (dfn[x] < dfn[y]) return x;
        return y;
    }
    void init() {
        int t = std::__lg(n) + 1;
        for (int i = 1; i <= n; i++) st[dfn[i]][0] = fa[i];
        for (int j = 1; j <= t; j++) {
            for (int i = 1; i + (1 << (j - 1)) <= n; i++) {
                st[i][j] = dfnmin(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
            }
        }
    }
    int query(int l, int r) {
        int t = std::__lg(r - l + 1);
        return dfnmin(st[l][t], st[r - (1 << t) + 1][t]);
    }
}st;

int Lca(int x, int y) {
    if (x == y) return x;
    if (dfn[x] > dfn[y]) std::swap(x, y);
    return st.query(dfn[x] + 1, dfn[y]);
}

int dis(int x, int y) {
    int l = Lca(x, y);
    return dep[x] + dep[y] - 2 * dep[l];
}

int fa2[MAXN], mx[MAXN], siz[MAXN];

int main() {
    std::ios::sync_with_stdio(0);
    std::cin.tie(0);
    std::cout.tie(0);

    std::cin >> n >> m;
    for (int i = 1; i <= n; i++) std::cin >> a[i];
    for (int i = 1; i < n; i++) {
        int u, v;
        std::cin >> u >> v;
        G[u].push_back(v);
        G[v].push_back(u);
    }
    dfs(1, 0);
    st.init();
    

    std::vector <bool> vis(n + 1);
    int node_cnt, rt;
    auto Find_cent = [&](int u, int f, auto self) -> void {
        mx[u] = 0;
        for (const auto &v : G[u]) {
            if (vis[v] || v == f) continue;
            self(v, u, self);
            mx[u] = std::max(mx[u], siz[v]);
        }
        mx[u] = std::max(mx[u], node_cnt - siz[u]);
        if (mx[rt] > mx[u]) rt = u;
    };

    auto Find_size = [&](int u, int f, auto self) -> void {
        siz[u] = 1;
        for (const auto &v : G[u]) {
            if (vis[v] || v == f) continue;
            self(v, u, self);
            siz[u] += siz[v];
        }
    };

    auto build = [&](int u, auto self) -> void {
        vis[u] = 1; Find_size(u, -1, Find_size);
        for (const auto &v : G[u]) {
            if (vis[v]) continue;
            mx[rt = 0] = 0x3f3f3f3f;
            node_cnt = siz[v];
            Find_cent(v, u, Find_cent);
            fa2[rt] = u;
            self(rt, self);
        }
    };

    mx[rt = 0] = 0x3f3f3f3f;
    node_cnt = n;
    Find_cent(1, 0, Find_cent);
    fa2[rt] = 0;
    build(rt, build);

    auto query = [&](int x, int k) {
        int f = fa2[x], cur = x, ret = t1.query(rt1[x], 0, n, 0, k);
        while (f) {
            int d = k - dis(x, f);
            ret += (t1.query(rt1[f], 0, n, 0, d) - t2.query(rt2[cur], 0, n, 0, d));
            cur = fa2[cur];
            f = fa2[f];
        }
        return ret;
    };

    auto update = [&](int x, int y, bool flag) {
        int cur = x;
        while (cur) {
            if (flag) t1.update(rt1[cur], 0, n, dis(x, cur), -a[x]);
            t1.update(rt1[cur], 0, n, dis(x, cur), y);
            int f = fa2[cur];
            if (f) {
                if (flag) t2.update(rt2[cur], 0, n, dis(x, f), -a[x]);
                t2.update(rt2[cur], 0, n, dis(x, f), y);
            }
            cur = f;
        }
    };
    
    for (int i = 1; i <= n; i++) {
        update(i, a[i], 0);
    }

    while (m--) {
        int op, x, y;
        std::cin >> op >> x >> y;
        x ^= lst; y ^= lst;
        if (op == 0) {
            std::cout << (lst = query(x, y)) << '\n';
        } 
        if (op == 1) {
            update(x, y, 1);
            a[x] = y;
        }
    }
    return 0;
}
posted @ 2026-01-19 21:32  Katyusha_Lzh  阅读(0)  评论(0)    收藏  举报