Selaa lähdekoodia

课程:同步最新讲义与高阶扩展及参考实现

- 清理历史:最早提交移除 problems/ 并添加 .gitignore
- 新增 8 篇高阶扩展与 20 个参考实现附件
- 更新主讲义与总索引至最新完成状态

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
Codex Agent 1 kuukausi sitten
vanhempi
sitoutus
806e2761fe
40 muutettua tiedostoa jossa 3402 lisäystä ja 53 poistoa
  1. 89 0
      参考实现/01-欧拉路径/AtCoder-agc018_f.cpp
  2. 71 0
      参考实现/01-欧拉路径/AtCoder-agc032_c.cpp
  3. 78 0
      参考实现/01-欧拉路径/CodeForces-1361C.cpp
  4. 110 0
      参考实现/02-强连通与双连通/AtCoder-agc071_c.cpp
  5. 58 0
      参考实现/03-Bitset/CodeForces-986C.cpp
  6. 121 0
      参考实现/04-KMP与Manacher/AtCoder-qupc2018_h.cpp
  7. 45 0
      参考实现/05-最短路与类最短路/AtCoder-agc056_c.cpp
  8. 50 0
      参考实现/05-最短路与类最短路/CodeForces-1340C.cpp
  9. 70 0
      参考实现/06-最小与最大生成树/AtCoder-agc014_e.cpp
  10. 74 0
      参考实现/06-最小与最大生成树/AtCoder-agc064_b.cpp
  11. 52 0
      参考实现/06-最小与最大生成树/CodeForces-1408E.cpp
  12. 34 0
      参考实现/07-二分图/AtCoder-agc025_d.cpp
  13. 74 0
      参考实现/07-二分图/CodeForces-1634E.cpp
  14. 112 0
      参考实现/08-高级线段树与树状数组/AtCoder-agc023_e.cpp
  15. 40 0
      参考实现/09-基环树/CodeForces-1270G.cpp
  16. 136 0
      参考实现/09-基环树/CodeForces-835F.cpp
  17. 61 0
      参考实现/10-高斯消元与CRT/AtCoder-abc366_g.cpp
  18. 103 0
      参考实现/10-高斯消元与CRT/AtCoder-agc050_e.cpp
  19. 98 0
      参考实现/10-高斯消元与CRT/CodeForces-724G.cpp
  20. 105 0
      参考实现/11-平衡树/CodeForces-896C.cpp
  21. 64 43
      课程/00-CF2400-2700高阶扩展路线.md
  22. 134 0
      课程/01-欧拉路径/01-判定构造与建模.md
  23. 93 0
      课程/01-欧拉路径/02-高阶扩展-掩码图上的欧拉回路.md
  24. 72 0
      课程/02-强连通与双连通/03-割点点双连通与圆方树入口.md
  25. 102 0
      课程/03-Bitset/02-高阶扩展-补集子掩码隐式图.md
  26. 70 0
      课程/04-KMP与Manacher/02-Manacher回文半径与区间计数.md
  27. 96 0
      课程/05-最短路与类最短路/03-最短路树次短路与差分约束.md
  28. 98 0
      课程/05-最短路与类最短路/04-高阶扩展-周期内进度状态.md
  29. 88 0
      课程/06-最小与最大生成树/03-Kruskal重构树与XORMST.md
  30. 91 0
      课程/06-最小与最大生成树/04-高阶扩展-关联图中的最大森林.md
  31. 87 0
      课程/07-二分图/01-二分染色与构造选择.md
  32. 83 0
      课程/07-二分图/04-高阶扩展-关联图欧拉平衡.md
  33. 111 0
      课程/08-高级线段树与树状数组/01-Fenwick离线计数与序关系.md
  34. 54 0
      课程/09-基环树/01-剥叶找环与环上统计.md
  35. 127 0
      课程/09-基环树/02-高阶扩展-删环边直径与代数构造.md
  36. 81 0
      课程/10-高斯消元与CRT/01-高斯消元秩解空间与XOR系统.md
  37. 143 0
      课程/10-高斯消元与CRT/02-CRT与非互质同余合并.md
  38. 103 0
      课程/10-高斯消元与CRT/03-高阶扩展-XOR路径的环空间.md
  39. 100 0
      课程/11-平衡树/04-高阶扩展-随机输入契约与ODT.md
  40. 24 10
      课程/README.md

+ 89 - 0
参考实现/01-欧拉路径/AtCoder-agc018_f.cpp

@@ -0,0 +1,89 @@
+#include <bits/stdc++.h>
+using namespace std;
+
+struct Edge {
+    int to;
+    int id;
+};
+
+int main() {
+    ios::sync_with_stdio(false);
+    cin.tie(nullptr);
+
+    int n;
+    cin >> n;
+    vector<int> parent_a(n), parent_b(n);
+    for (int &x : parent_a) {
+        cin >> x;
+        if (x != -1) --x;
+    }
+    for (int &x : parent_b) {
+        cin >> x;
+        if (x != -1) --x;
+    }
+
+    vector<int> children_a(n), children_b(n);
+    int root_a = -1, root_b = -1;
+    for (int v = 0; v < n; ++v) {
+        if (parent_a[v] == -1) root_a = v;
+        else ++children_a[parent_a[v]];
+        if (parent_b[v] == -1) root_b = v;
+        else ++children_b[parent_b[v]];
+    }
+    for (int v = 0; v < n; ++v) {
+        if ((children_a[v] & 1) != (children_b[v] & 1)) {
+            cout << "IMPOSSIBLE\n";
+            return 0;
+        }
+    }
+
+    const int extra = 2 * n;
+    vector<vector<Edge>> graph(2 * n + 1);
+    vector<pair<int, int>> endpoints;
+    vector<int> cross_vertex;
+    auto add_edge = [&](int u, int v, int owner) {
+        int id = static_cast<int>(endpoints.size());
+        endpoints.push_back({u, v});
+        cross_vertex.push_back(owner);
+        graph[u].push_back({v, id});
+        graph[v].push_back({u, id});
+    };
+
+    for (int v = 0; v < n; ++v) {
+        if (parent_a[v] != -1) add_edge(v, parent_a[v], -1);
+        if (parent_b[v] != -1) add_edge(n + v, n + parent_b[v], -1);
+    }
+    add_edge(root_a, extra, -1);
+    add_edge(n + root_b, extra, -1);
+    for (int v = 0; v < n; ++v) {
+        if ((children_a[v] & 1) == 0) add_edge(v, n + v, v);
+    }
+
+    vector<int> answer(n, 0), cursor(graph.size(), 0), stack = {extra};
+    vector<char> used(endpoints.size(), false);
+    while (!stack.empty()) {
+        int v = stack.back();
+        while (cursor[v] < static_cast<int>(graph[v].size()) &&
+               used[graph[v][cursor[v]].id]) {
+            ++cursor[v];
+        }
+        if (cursor[v] == static_cast<int>(graph[v].size())) {
+            stack.pop_back();
+            continue;
+        }
+        Edge e = graph[v][cursor[v]++];
+        if (used[e.id]) continue;
+        used[e.id] = true;
+        int owner = cross_vertex[e.id];
+        if (owner != -1) answer[owner] = (v == owner ? 1 : -1);
+        stack.push_back(e.to);
+    }
+
+    cout << "POSSIBLE\n";
+    for (int i = 0; i < n; ++i) {
+        if (i) cout << ' ';
+        cout << answer[i];
+    }
+    cout << '\n';
+    return 0;
+}

+ 71 - 0
参考实现/01-欧拉路径/AtCoder-agc032_c.cpp

@@ -0,0 +1,71 @@
+#include <bits/stdc++.h>
+using namespace std;
+
+int main() {
+    ios::sync_with_stdio(false);
+    cin.tie(nullptr);
+
+    int n, m;
+    cin >> n >> m;
+    vector<vector<int>> graph(n);
+    vector<int> degree(n, 0);
+    for (int i = 0; i < m; ++i) {
+        int u, v;
+        cin >> u >> v;
+        --u;
+        --v;
+        graph[u].push_back(v);
+        graph[v].push_back(u);
+        ++degree[u];
+        ++degree[v];
+    }
+    for (int d : degree) {
+        if (d & 1) {
+            cout << "No\n";
+            return 0;
+        }
+    }
+    for (int d : degree) {
+        if (d >= 6) {
+            cout << "Yes\n";
+            return 0;
+        }
+    }
+
+    vector<int> four;
+    for (int v = 0; v < n; ++v) {
+        if (degree[v] == 4) four.push_back(v);
+    }
+    if (four.size() >= 3) {
+        cout << "Yes\n";
+        return 0;
+    }
+    if (four.size() <= 1) {
+        cout << "No\n";
+        return 0;
+    }
+
+    const int start = four[0], forbidden = four[1];
+    vector<char> visited(n, false);
+    stack<pair<int, int>> work;
+    for (int to : graph[start]) {
+        if (to != forbidden) work.push({to, start});
+    }
+    visited[start] = true;
+    bool found_cycle_avoiding_other = false;
+    while (!work.empty()) {
+        auto [v, parent] = work.top();
+        work.pop();
+        if (v == start) {
+            found_cycle_avoiding_other = true;
+            break;
+        }
+        if (v == forbidden || visited[v]) continue;
+        visited[v] = true;
+        for (int to : graph[v]) {
+            if (to != parent) work.push({to, v});
+        }
+    }
+    cout << (found_cycle_avoiding_other ? "Yes\n" : "No\n");
+    return 0;
+}

+ 78 - 0
参考实现/01-欧拉路径/CodeForces-1361C.cpp

@@ -0,0 +1,78 @@
+#include <bits/stdc++.h>
+using namespace std;
+
+struct Step {
+    int edge;
+    int from;
+    int to;
+};
+
+int main() {
+    ios::sync_with_stdio(false);
+    cin.tie(nullptr);
+
+    int n;
+    cin >> n;
+    vector<int> a(n), b(n);
+    for (int i = 0; i < n; ++i) cin >> a[i] >> b[i];
+
+    for (int bits = 20; bits >= 0; --bits) {
+        int vertices = 1 << bits;
+        int mask = vertices - 1;
+        vector<vector<pair<int, int>>> graph(vertices);
+        vector<int> degree(vertices, 0);
+        for (int i = 0; i < n; ++i) {
+            int u = a[i] & mask;
+            int v = b[i] & mask;
+            graph[u].push_back({v, i});
+            graph[v].push_back({u, i});
+            ++degree[u];
+            ++degree[v];
+        }
+        bool possible = true;
+        int start = a[0] & mask;
+        for (int v = 0; v < vertices; ++v) {
+            if (degree[v] & 1) possible = false;
+            if (degree[v] != 0) start = v;
+        }
+        if (!possible) continue;
+
+        vector<int> iterator(vertices, 0);
+        vector<char> used(n, false);
+        vector<int> vertex_stack{start};
+        vector<Step> entering;
+        vector<Step> tour;
+        while (!vertex_stack.empty()) {
+            int v = vertex_stack.back();
+            while (iterator[v] < static_cast<int>(graph[v].size())
+                   && used[graph[v][iterator[v]].second]) {
+                ++iterator[v];
+            }
+            if (iterator[v] == static_cast<int>(graph[v].size())) {
+                vertex_stack.pop_back();
+                if (!entering.empty()) {
+                    tour.push_back(entering.back());
+                    entering.pop_back();
+                }
+                continue;
+            }
+            auto [to, edge] = graph[v][iterator[v]++];
+            if (used[edge]) continue;
+            used[edge] = true;
+            vertex_stack.push_back(to);
+            entering.push_back({edge, v, to});
+        }
+        if (static_cast<int>(tour.size()) != n) continue;
+        reverse(tour.begin(), tour.end());
+
+        cout << bits << '\n';
+        for (const Step &step : tour) {
+            int left = 2 * step.edge + 1;
+            int right = 2 * step.edge + 2;
+            if ((a[step.edge] & mask) != step.from) swap(left, right);
+            cout << left << ' ' << right << ' ';
+        }
+        cout << '\n';
+        return 0;
+    }
+}

+ 110 - 0
参考实现/02-强连通与双连通/AtCoder-agc071_c.cpp

@@ -0,0 +1,110 @@
+#include <bits/stdc++.h>
+using namespace std;
+
+struct Edge {
+    int to;
+    int id;
+};
+
+int main() {
+    ios::sync_with_stdio(false);
+    cin.tie(nullptr);
+
+    int tests;
+    cin >> tests;
+    while (tests--) {
+        int n, m;
+        cin >> n >> m;
+        vector<pair<int, int>> ends(m);
+        vector<vector<Edge>> graph(n);
+        for (int id = 0; id < m; ++id) {
+            int u, v;
+            cin >> u >> v;
+            --u;
+            --v;
+            ends[id] = {u, v};
+            graph[u].push_back({v, id});
+            graph[v].push_back({u, id});
+        }
+
+        bool bipartite = true;
+        vector<int> color(n, -1);
+        queue<int> bfs;
+        color[0] = 0;
+        bfs.push(0);
+        while (!bfs.empty()) {
+            int u = bfs.front();
+            bfs.pop();
+            for (Edge e : graph[u]) {
+                if (color[e.to] == -1) {
+                    color[e.to] = color[u] ^ 1;
+                    bfs.push(e.to);
+                } else if (color[e.to] == color[u]) {
+                    bipartite = false;
+                }
+            }
+        }
+
+        vector<int> dfn(n, 0), low(n, 0), edge_stack;
+        vector<vector<int>> groups(n);
+        vector<int> parent_vertex(n, -1), parent_edge(n, -1), cursor(n, 0);
+        vector<int> incident_count(n, 0), touched;
+        int timer = 1;
+        dfn[0] = low[0] = 1;
+        vector<int> dfs_stack = {0};
+        while (!dfs_stack.empty()) {
+            int u = dfs_stack.back();
+            if (cursor[u] < static_cast<int>(graph[u].size())) {
+                Edge e = graph[u][cursor[u]++];
+                if (e.id == parent_edge[u]) continue;
+                if (!dfn[e.to]) {
+                    parent_vertex[e.to] = u;
+                    parent_edge[e.to] = e.id;
+                    edge_stack.push_back(e.id);
+                    dfn[e.to] = low[e.to] = ++timer;
+                    dfs_stack.push_back(e.to);
+                } else if (dfn[e.to] < dfn[u]) {
+                    edge_stack.push_back(e.id);
+                    low[u] = min(low[u], dfn[e.to]);
+                }
+                continue;
+            }
+
+            dfs_stack.pop_back();
+            int parent = parent_vertex[u];
+            if (parent == -1) continue;
+            low[parent] = min(low[parent], low[u]);
+            if (low[u] < dfn[parent]) continue;
+
+            touched.clear();
+            while (true) {
+                int id = edge_stack.back();
+                edge_stack.pop_back();
+                for (int v : {ends[id].first, ends[id].second}) {
+                    if (incident_count[v] == 0) touched.push_back(v);
+                    ++incident_count[v];
+                }
+                if (id == parent_edge[u]) break;
+            }
+            for (int v : touched) {
+                groups[v].push_back(incident_count[v]);
+                incident_count[v] = 0;
+            }
+        }
+
+        bool every_degree_is_formable = true;
+        for (auto &sizes : groups) {
+            sort(sizes.begin(), sizes.end());
+            int covered = 0;
+            for (int size : sizes) {
+                if (size > covered + 1) {
+                    every_degree_is_formable = false;
+                    break;
+                }
+                covered += size;
+            }
+        }
+        cout << (!(bipartite && every_degree_is_formable) ? "Yes\n" : "No\n");
+    }
+    return 0;
+}

+ 58 - 0
参考实现/03-Bitset/CodeForces-986C.cpp

@@ -0,0 +1,58 @@
+#include <bits/stdc++.h>
+using namespace std;
+
+int main() {
+    ios::sync_with_stdio(false);
+    cin.tie(nullptr);
+
+    int n, m;
+    cin >> n >> m;
+    int size = 1 << n;
+    int full = size - 1;
+    vector<char> present(size, false), seen_value(size, false), seen_mask(size, false);
+    vector<int> values(m);
+    for (int &x : values) {
+        cin >> x;
+        present[x] = true;
+    }
+
+    auto explore_component = [&](int source) {
+        vector<pair<int, int>> stack;
+        seen_value[source] = true;
+        stack.push_back({source, 0});  // type 0: real value, type 1: submask state
+        while (!stack.empty()) {
+            auto [x, type] = stack.back();
+            stack.pop_back();
+            if (type == 0) {
+                int mask = full ^ x;
+                if (!seen_mask[mask]) {
+                    seen_mask[mask] = true;
+                    stack.push_back({mask, 1});
+                }
+            } else {
+                if (present[x] && !seen_value[x]) {
+                    seen_value[x] = true;
+                    stack.push_back({x, 0});
+                }
+                for (int bits = x; bits != 0; bits &= bits - 1) {
+                    int bit = bits & -bits;
+                    int next = x ^ bit;
+                    if (!seen_mask[next]) {
+                        seen_mask[next] = true;
+                        stack.push_back({next, 1});
+                    }
+                }
+            }
+        }
+    };
+
+    int components = 0;
+    for (int x : values) {
+        if (!seen_value[x]) {
+            ++components;
+            explore_component(x);
+        }
+    }
+    cout << components << '\n';
+    return 0;
+}

+ 121 - 0
参考实现/04-KMP与Manacher/AtCoder-qupc2018_h.cpp

@@ -0,0 +1,121 @@
+#include <bits/stdc++.h>
+using namespace std;
+
+struct DSU {
+    vector<int> parent;
+    explicit DSU(int n = 0) : parent(n) { iota(parent.begin(), parent.end(), 0); }
+    int find(int x) {
+        while (parent[x] != x) {
+            parent[x] = parent[parent[x]];
+            x = parent[x];
+        }
+        return x;
+    }
+    void unite(int a, int b) {
+        a = find(a);
+        b = find(b);
+        if (a != b) parent[b] = a;
+    }
+};
+
+int main() {
+    ios::sync_with_stdio(false);
+    cin.tie(nullptr);
+
+    int n;
+    cin >> n;
+    vector<int> radius(n);
+    for (int &x : radius) {
+        cin >> x;
+        x = (x - 1) / 2;
+    }
+
+    int levels = 1;
+    while ((1 << levels) <= n) ++levels;
+    vector<int> count(levels), length(levels);
+    vector<DSU> dsu;
+    dsu.reserve(levels);
+    for (int k = 0; k < levels; ++k) {
+        length[k] = 1 << k;
+        count[k] = n - length[k] + 1;
+        dsu.emplace_back(2 * count[k]);
+    }
+    for (int i = 0; i < n; ++i) dsu[0].unite(i, count[0] + i);
+
+    auto node = [&](int k, int start, bool reversed) {
+        return start + (reversed ? count[k] : 0);
+    };
+    for (int center = 0; center < n; ++center) {
+        int remaining = radius[center];
+        int offset = 0;
+        while (remaining > 0) {
+            int k = 31 - __builtin_clz(remaining);
+            int block = 1 << k;
+            int left_start = center - radius[center] + offset;
+            int right_start = center + radius[center] - offset - block + 1;
+            dsu[k].unite(node(k, left_start, false), node(k, right_start, true));
+            offset += block;
+            remaining -= block;
+        }
+    }
+
+    for (int k = levels - 1; k >= 1; --k) {
+        int half = 1 << (k - 1);
+        auto children = [&](int id) {
+            bool reversed = id >= count[k];
+            int start = id - (reversed ? count[k] : 0);
+            if (!reversed) {
+                return pair<int, int>{node(k - 1, start, false),
+                                      node(k - 1, start + half, false)};
+            }
+            return pair<int, int>{node(k - 1, start + half, true),
+                                  node(k - 1, start, true)};
+        };
+        for (int id = 0; id < 2 * count[k]; ++id) {
+            int root = dsu[k].find(id);
+            if (root == id) continue;
+            auto [a1, a2] = children(id);
+            auto [b1, b2] = children(root);
+            dsu[k - 1].unite(a1, b1);
+            dsu[k - 1].unite(a2, b2);
+        }
+    }
+
+    vector<vector<int>> unequal(n);
+    for (int center = 0; center < n; ++center) {
+        int left = center - radius[center] - 1;
+        int right = center + radius[center] + 1;
+        if (left >= 0 && right < n) {
+            int a = dsu[0].find(left);
+            int b = dsu[0].find(right);
+            unequal[a].push_back(b);
+            unequal[b].push_back(a);
+        }
+    }
+
+    vector<vector<int>> members(n);
+    for (int i = 0; i < n; ++i) members[dsu[0].find(i)].push_back(i);
+    vector<int> roots;
+    for (int i = 0; i < n; ++i) if (!members[i].empty()) roots.push_back(i);
+    sort(roots.begin(), roots.end(), [&](int a, int b) {
+        return members[a][0] < members[b][0];
+    });
+
+    vector<int> color(n, -1);
+    for (int root : roots) {
+        bool forbidden[26] = {};
+        for (int to : unequal[root]) {
+            to = dsu[0].find(to);
+            if (color[to] != -1) forbidden[color[to]] = true;
+        }
+        int chosen = 0;
+        while (chosen < 26 && forbidden[chosen]) ++chosen;
+        if (chosen == 26) return 1;  // The statement guarantees this cannot happen.
+        color[root] = chosen;
+    }
+
+    string answer(n, 'a');
+    for (int i = 0; i < n; ++i) answer[i] += color[dsu[0].find(i)];
+    cout << answer << '\n';
+    return 0;
+}

+ 45 - 0
参考实现/05-最短路与类最短路/AtCoder-agc056_c.cpp

@@ -0,0 +1,45 @@
+#include <bits/stdc++.h>
+using namespace std;
+
+int main() {
+    ios::sync_with_stdio(false);
+    cin.tie(nullptr);
+
+    int n, m;
+    cin >> n >> m;
+    vector<vector<pair<int, int>>> graph(n + 1);
+    for (int i = 0; i < n; ++i) {
+        graph[i].push_back({i + 1, 1});
+        graph[i + 1].push_back({i, 1});
+    }
+    for (int i = 0; i < m; ++i) {
+        int l, r;
+        cin >> l >> r;
+        --l;
+        graph[l].push_back({r, 0});
+        graph[r].push_back({l, 0});
+    }
+
+    const int inf = numeric_limits<int>::max() / 4;
+    vector<int> distance(n + 1, inf);
+    deque<int> queue;
+    distance[0] = 0;
+    queue.push_back(0);
+    while (!queue.empty()) {
+        int u = queue.front();
+        queue.pop_front();
+        for (auto [v, weight] : graph[u]) {
+            if (distance[v] <= distance[u] + weight) continue;
+            distance[v] = distance[u] + weight;
+            if (weight == 0) queue.push_front(v);
+            else queue.push_back(v);
+        }
+    }
+    string answer;
+    answer.reserve(n);
+    for (int i = 1; i <= n; ++i) {
+        answer.push_back(distance[i] > distance[i - 1] ? '0' : '1');
+    }
+    cout << answer << '\n';
+    return 0;
+}

+ 50 - 0
参考实现/05-最短路与类最短路/CodeForces-1340C.cpp

@@ -0,0 +1,50 @@
+#include <bits/stdc++.h>
+using namespace std;
+
+int main() {
+    ios::sync_with_stdio(false);
+    cin.tie(nullptr);
+
+    int road_length, m;
+    cin >> road_length >> m;
+    vector<int> position(m);
+    for (int &x : position) cin >> x;
+    sort(position.begin(), position.end());
+    int green, red;
+    cin >> green >> red;
+
+    const int INF = numeric_limits<int>::max() / 4;
+    vector<int> distance(m * green, INF);
+    auto id = [green](int island, int spent) { return island * green + spent; };
+    int start = lower_bound(position.begin(), position.end(), 0) - position.begin();
+    deque<pair<int, int>> queue;
+    distance[id(start, 0)] = 0;
+    queue.push_back({start, 0});
+    long long answer = numeric_limits<long long>::max();
+
+    while (!queue.empty()) {
+        auto [island, spent] = queue.front();
+        queue.pop_front();
+        int completed = distance[id(island, spent)];
+        int to_finish = road_length - position[island];
+        if (to_finish <= green - spent) {
+            answer = min(answer, 1LL * completed * (green + red) + spent + to_finish);
+        }
+        for (int direction : {-1, 1}) {
+            int next_island = island + direction;
+            if (next_island < 0 || next_island >= m) continue;
+            int next_spent = spent + abs(position[next_island] - position[island]);
+            if (next_spent > green) continue;
+            int add = (next_spent == green);
+            if (add) next_spent = 0;
+            int state = id(next_island, next_spent);
+            if (completed + add >= distance[state]) continue;
+            distance[state] = completed + add;
+            if (add) queue.push_back({next_island, next_spent});
+            else queue.push_front({next_island, next_spent});
+        }
+    }
+    if (answer == numeric_limits<long long>::max()) cout << -1 << '\n';
+    else cout << answer << '\n';
+    return 0;
+}

+ 70 - 0
参考实现/06-最小与最大生成树/AtCoder-agc014_e.cpp

@@ -0,0 +1,70 @@
+#include <bits/stdc++.h>
+using namespace std;
+
+struct DSU {
+    vector<int> parent;
+    explicit DSU(int n) : parent(n) { iota(parent.begin(), parent.end(), 0); }
+    int find(int x) { return parent[x] == x ? x : parent[x] = find(parent[x]); }
+};
+
+int main() {
+    ios::sync_with_stdio(false);
+    cin.tie(nullptr);
+
+    int n;
+    cin >> n;
+    vector<unordered_map<int, int>> adjacent(n);
+    auto add = [&](int u, int v, int bit) {
+        adjacent[u][v] |= bit;
+        adjacent[v][u] |= bit;
+    };
+    for (int i = 0; i < n - 1; ++i) {
+        int u, v;
+        cin >> u >> v;
+        add(--u, --v, 1);
+    }
+    for (int i = 0; i < n - 1; ++i) {
+        int u, v;
+        cin >> u >> v;
+        add(--u, --v, 2);
+    }
+
+    queue<pair<int, int>> ready;
+    for (int u = 0; u < n; ++u) {
+        for (auto [v, mask] : adjacent[u]) {
+            if (u < v && mask == 3) ready.push({u, v});
+        }
+    }
+
+    DSU dsu(n);
+    int components = n;
+    while (!ready.empty()) {
+        auto [raw_u, raw_v] = ready.front();
+        ready.pop();
+        int u = dsu.find(raw_u), v = dsu.find(raw_v);
+        if (u == v) continue;
+        auto it = adjacent[u].find(v);
+        if (it == adjacent[u].end() || it->second != 3) continue;
+        if (adjacent[u].size() < adjacent[v].size()) swap(u, v);
+
+        adjacent[u].erase(v);
+        adjacent[v].erase(u);
+        vector<pair<int, int>> moved(adjacent[v].begin(), adjacent[v].end());
+        for (auto [raw_to, mask] : moved) {
+            int to = dsu.find(raw_to);
+            if (to == u || to == v) continue;
+            adjacent[to].erase(v);
+            int merged_mask = mask;
+            auto old = adjacent[u].find(to);
+            if (old != adjacent[u].end()) merged_mask |= old->second;
+            adjacent[u][to] = merged_mask;
+            adjacent[to][u] = merged_mask;
+            if (merged_mask == 3) ready.push({u, to});
+        }
+        adjacent[v].clear();
+        dsu.parent[v] = u;
+        --components;
+    }
+    cout << (components == 1 ? "YES\n" : "NO\n");
+    return 0;
+}

+ 74 - 0
参考实现/06-最小与最大生成树/AtCoder-agc064_b.cpp

@@ -0,0 +1,74 @@
+#include <bits/stdc++.h>
+using namespace std;
+
+struct DSU {
+    vector<int> parent, size;
+    explicit DSU(int n) : parent(n), size(n, 1) { iota(parent.begin(), parent.end(), 0); }
+    int find(int x) { return parent[x] == x ? x : parent[x] = find(parent[x]); }
+    bool unite(int a, int b) {
+        a = find(a); b = find(b);
+        if (a == b) return false;
+        if (size[a] < size[b]) swap(a, b);
+        parent[b] = a; size[a] += size[b];
+        return true;
+    }
+};
+
+struct Edge { int u, v; char color; };
+
+int main() {
+    ios::sync_with_stdio(false);
+    cin.tie(nullptr);
+
+    int n, m;
+    cin >> n >> m;
+    vector<Edge> edges(m);
+    for (Edge &e : edges) {
+        cin >> e.u >> e.v >> e.color;
+        --e.u; --e.v;
+    }
+    string wanted;
+    cin >> wanted;
+
+    DSU dsu(n);
+    vector<char> chosen(m, false), satisfied(n, false);
+    vector<vector<pair<int, int>>> outgoing(n);
+    for (int id = 0; id < m; ++id) {
+        Edge e = edges[id];
+        bool good_u = wanted[e.u] == e.color;
+        bool good_v = wanted[e.v] == e.color;
+        if (good_u && good_v && dsu.unite(e.u, e.v)) {
+            chosen[id] = true;
+            satisfied[e.u] = satisfied[e.v] = true;
+        } else if (good_u != good_v) {
+            int from = good_u ? e.v : e.u;
+            int to = good_u ? e.u : e.v;
+            outgoing[from].push_back({to, id});
+        }
+    }
+
+    queue<int> queue;
+    for (int v = 0; v < n; ++v) if (satisfied[v]) queue.push(v);
+    while (!queue.empty()) {
+        int u = queue.front();
+        queue.pop();
+        for (auto [v, id] : outgoing[u]) {
+            if (satisfied[v]) continue;
+            satisfied[v] = true;
+            chosen[id] = true;
+            dsu.unite(u, v);
+            queue.push(v);
+        }
+    }
+    if (find(satisfied.begin(), satisfied.end(), false) != satisfied.end()) {
+        cout << "No\n";
+        return 0;
+    }
+    for (int id = 0; id < m; ++id) {
+        if (!chosen[id] && dsu.unite(edges[id].u, edges[id].v)) chosen[id] = true;
+    }
+    cout << "Yes\n";
+    for (int id = 0; id < m; ++id) if (chosen[id]) cout << id + 1 << ' ';
+    cout << '\n';
+    return 0;
+}

+ 52 - 0
参考实现/06-最小与最大生成树/CodeForces-1408E.cpp

@@ -0,0 +1,52 @@
+#include <bits/stdc++.h>
+using namespace std;
+
+struct DSU {
+    vector<int> parent, size;
+    explicit DSU(int n) : parent(n), size(n, 1) { iota(parent.begin(), parent.end(), 0); }
+    int find(int x) { return parent[x] == x ? x : parent[x] = find(parent[x]); }
+    bool unite(int a, int b) {
+        a = find(a); b = find(b);
+        if (a == b) return false;
+        if (size[a] < size[b]) swap(a, b);
+        parent[b] = a; size[a] += size[b];
+        return true;
+    }
+};
+
+struct Edge { int u, v; long long weight; };
+
+int main() {
+    ios::sync_with_stdio(false);
+    cin.tie(nullptr);
+
+    int sets, values;
+    cin >> sets >> values;
+    vector<long long> a(sets), b(values);
+    for (long long &x : a) cin >> x;
+    for (long long &x : b) cin >> x;
+    vector<Edge> edges;
+    long long total = 0;
+    for (int i = 0; i < sets; ++i) {
+        int size;
+        cin >> size;
+        while (size--) {
+            int value;
+            cin >> value;
+            --value;
+            long long weight = a[i] + b[value];
+            edges.push_back({i, sets + value, weight});
+            total += weight;
+        }
+    }
+    sort(edges.begin(), edges.end(), [](const Edge &x, const Edge &y) {
+        return x.weight > y.weight;
+    });
+    DSU dsu(sets + values);
+    long long kept = 0;
+    for (const Edge &edge : edges) {
+        if (dsu.unite(edge.u, edge.v)) kept += edge.weight;
+    }
+    cout << total - kept << '\n';
+    return 0;
+}

+ 34 - 0
参考实现/07-二分图/AtCoder-agc025_d.cpp

@@ -0,0 +1,34 @@
+#include <bits/stdc++.h>
+using namespace std;
+
+int color_for_distance(int x, int y, int distance_squared) {
+    while (distance_squared % 4 == 0) {
+        distance_squared /= 4;
+        x /= 2;
+        y /= 2;
+    }
+    if (distance_squared & 1) return (x + y) & 1;
+    return x & 1;
+}
+
+int main() {
+    ios::sync_with_stdio(false);
+    cin.tie(nullptr);
+
+    int n, d1, d2;
+    cin >> n >> d1 >> d2;
+    vector<vector<pair<int, int>>> bucket(4);
+    for (int x = 0; x < 2 * n; ++x) {
+        for (int y = 0; y < 2 * n; ++y) {
+            int key = 2 * color_for_distance(x, y, d1)
+                    + color_for_distance(x, y, d2);
+            bucket[key].push_back({x, y});
+        }
+    }
+    auto best = max_element(bucket.begin(), bucket.end(),
+                            [](const auto &a, const auto &b) { return a.size() < b.size(); });
+    for (int i = 0; i < n * n; ++i) {
+        cout << (*best)[i].first << ' ' << (*best)[i].second << '\n';
+    }
+    return 0;
+}

+ 74 - 0
参考实现/07-二分图/CodeForces-1634E.cpp

@@ -0,0 +1,74 @@
+#include <bits/stdc++.h>
+using namespace std;
+
+int main() {
+    ios::sync_with_stdio(false);
+    cin.tie(nullptr);
+
+    int arrays;
+    cin >> arrays;
+    vector<vector<long long>> input(arrays);
+    vector<long long> all_values;
+    int edge_count = 0;
+    for (int i = 0; i < arrays; ++i) {
+        int length;
+        cin >> length;
+        input[i].resize(length);
+        for (long long &x : input[i]) {
+            cin >> x;
+            all_values.push_back(x);
+        }
+        edge_count += length;
+    }
+    sort(all_values.begin(), all_values.end());
+    all_values.erase(unique(all_values.begin(), all_values.end()), all_values.end());
+    int value_count = static_cast<int>(all_values.size());
+    vector<vector<pair<int, int>>> graph(arrays + value_count);
+    vector<pair<int, int>> location(edge_count);
+    vector<int> value_degree(value_count, 0);
+    int edge = 0;
+    for (int i = 0; i < arrays; ++i) {
+        for (int j = 0; j < static_cast<int>(input[i].size()); ++j) {
+            int value = lower_bound(all_values.begin(), all_values.end(), input[i][j]) - all_values.begin();
+            int node = arrays + value;
+            graph[i].push_back({node, edge});
+            graph[node].push_back({i, edge});
+            location[edge] = {i, j};
+            ++value_degree[value];
+            ++edge;
+        }
+    }
+    for (int degree : value_degree) {
+        if (degree & 1) {
+            cout << "NO\n";
+            return 0;
+        }
+    }
+
+    vector<string> answer(arrays);
+    for (int i = 0; i < arrays; ++i) answer[i].assign(input[i].size(), '?');
+    vector<int> iterator(graph.size(), 0);
+    vector<char> used(edge_count, false);
+    for (int source = 0; source < static_cast<int>(graph.size()); ++source) {
+        if (iterator[source] == static_cast<int>(graph[source].size())) continue;
+        vector<int> stack{source};
+        while (!stack.empty()) {
+            int v = stack.back();
+            while (iterator[v] < static_cast<int>(graph[v].size())
+                   && used[graph[v][iterator[v]].second]) ++iterator[v];
+            if (iterator[v] == static_cast<int>(graph[v].size())) {
+                stack.pop_back();
+                continue;
+            }
+            auto [to, id] = graph[v][iterator[v]++];
+            if (used[id]) continue;
+            used[id] = true;
+            auto [array, position] = location[id];
+            answer[array][position] = (v < arrays ? 'L' : 'R');
+            stack.push_back(to);
+        }
+    }
+    cout << "YES\n";
+    for (const string &line : answer) cout << line << '\n';
+    return 0;
+}

+ 112 - 0
参考实现/08-高级线段树与树状数组/AtCoder-agc023_e.cpp

@@ -0,0 +1,112 @@
+#include <bits/stdc++.h>
+using namespace std;
+
+constexpr long long MOD = 1'000'000'007LL;
+
+long long mod_power(long long base, long long exponent) {
+    long long result = 1;
+    while (exponent > 0) {
+        if (exponent & 1) result = result * base % MOD;
+        base = base * base % MOD;
+        exponent >>= 1;
+    }
+    return result;
+}
+
+struct Fenwick {
+    int n;
+    vector<long long> tree;
+    explicit Fenwick(int n) : n(n), tree(n + 1, 0) {}
+    void add(int index, long long value) {
+        value %= MOD;
+        for (; index <= n; index += index & -index) {
+            tree[index] += value;
+            if (tree[index] >= MOD) tree[index] -= MOD;
+        }
+    }
+    long long sum(int index) const {
+        long long result = 0;
+        for (; index > 0; index -= index & -index) result = (result + tree[index]) % MOD;
+        return result;
+    }
+    long long range_sum(int left, int right) const {
+        if (left > right) return 0;
+        return (sum(right) - sum(left - 1) + MOD) % MOD;
+    }
+};
+
+int main() {
+    ios::sync_with_stdio(false);
+    cin.tie(nullptr);
+
+    int n;
+    cin >> n;
+    vector<int> a(n), frequency(n + 1, 0);
+    for (int &x : a) {
+        cin >> x;
+        ++frequency[x];
+    }
+    vector<int> at_least(n + 2, 0), choices(n + 1, 0);
+    for (int value = n; value >= 1; --value) {
+        at_least[value] = at_least[value + 1] + frequency[value];
+        choices[value] = at_least[value] - (n - value);
+        if (choices[value] <= 0) {
+            cout << 0 << '\n';
+            return 0;
+        }
+    }
+
+    long long permutations = 1;
+    vector<long long> prefix(n + 1, 1), inverse_prefix(n + 1, 1);
+    vector<int> zero_count(n + 1, 0);
+    for (int value = 1; value <= n; ++value) {
+        permutations = permutations * choices[value] % MOD;
+        prefix[value] = prefix[value - 1];
+        zero_count[value] = zero_count[value - 1];
+        if (choices[value] == 1) {
+            ++zero_count[value];
+        } else {
+            prefix[value] = prefix[value] * (choices[value] - 1) % MOD;
+            prefix[value] = prefix[value] * mod_power(choices[value], MOD - 2) % MOD;
+        }
+        inverse_prefix[value] = mod_power(prefix[value], MOD - 2);
+    }
+
+    vector<int> group_left(n + 1), group_right(n + 1);
+    for (int left = 1; left <= n;) {
+        int right = left;
+        while (right + 1 <= n && zero_count[right + 1] == zero_count[left]) ++right;
+        for (int value = left; value <= right; ++value) {
+            group_left[value] = left;
+            group_right[value] = right;
+        }
+        left = right + 1;
+    }
+
+    Fenwick counts(n), sum_prefix(n), sum_inverse(n);
+    const long long inverse_two = (MOD + 1) / 2;
+    long long normalized = 0;
+    long long inversions_of_a = 0;
+    int inserted = 0;
+    for (int value : a) {
+        long long greater = inserted - counts.sum(value);
+        inversions_of_a = (inversions_of_a + greater) % MOD;
+
+        long long lower_inverse = sum_inverse.range_sum(group_left[value], value);
+        normalized = (normalized + prefix[value] * lower_inverse) % MOD;
+
+        long long higher_prefix = sum_prefix.range_sum(value + 1, group_right[value]);
+        normalized = (normalized - inverse_prefix[value] * higher_prefix) % MOD;
+        if (normalized < 0) normalized += MOD;
+
+        counts.add(value, 1);
+        sum_prefix.add(value, prefix[value]);
+        sum_inverse.add(value, inverse_prefix[value]);
+        ++inserted;
+    }
+
+    long long answer = permutations * inversions_of_a % MOD;
+    answer = (answer + permutations * inverse_two % MOD * normalized) % MOD;
+    cout << answer << '\n';
+    return 0;
+}

+ 40 - 0
参考实现/09-基环树/CodeForces-1270G.cpp

@@ -0,0 +1,40 @@
+#include <bits/stdc++.h>
+using namespace std;
+
+int main() {
+    ios::sync_with_stdio(false);
+    cin.tie(nullptr);
+
+    int tests;
+    cin >> tests;
+    while (tests--) {
+        int n;
+        cin >> n;
+        vector<int> next(n), indegree(n, 0);
+        for (int i = 0; i < n; ++i) {
+            int value;
+            cin >> value;
+            next[i] = i - value;
+            ++indegree[next[i]];
+        }
+        queue<int> leaves;
+        for (int i = 0; i < n; ++i) if (indegree[i] == 0) leaves.push(i);
+        while (!leaves.empty()) {
+            int v = leaves.front();
+            leaves.pop();
+            if (--indegree[next[v]] == 0) leaves.push(next[v]);
+        }
+        int start = 0;
+        while (indegree[start] == 0) ++start;
+        vector<int> cycle;
+        int v = start;
+        do {
+            cycle.push_back(v);
+            v = next[v];
+        } while (v != start);
+        cout << cycle.size() << '\n';
+        for (int x : cycle) cout << x + 1 << ' ';
+        cout << '\n';
+    }
+    return 0;
+}

+ 136 - 0
参考实现/09-基环树/CodeForces-835F.cpp

@@ -0,0 +1,136 @@
+#include <bits/stdc++.h>
+using namespace std;
+
+using int64 = long long;
+constexpr int64 NEG = numeric_limits<int64>::min() / 4;
+
+struct Summary {
+    int64 left = NEG;
+    int64 right = NEG;
+    int64 best = NEG;
+};
+
+Summary combine(const Summary &a, const Summary &b) {
+    if (a.left == NEG) return b;
+    if (b.left == NEG) return a;
+    return {max(a.left, b.left), max(a.right, b.right),
+            max({a.best, b.best, a.left + b.right})};
+}
+
+struct SegmentTree {
+    int size = 1;
+    vector<Summary> tree;
+    explicit SegmentTree(const vector<Summary> &values) {
+        while (size < static_cast<int>(values.size())) size <<= 1;
+        tree.assign(2 * size, {});
+        for (int i = 0; i < static_cast<int>(values.size()); ++i) tree[size + i] = values[i];
+        for (int i = size - 1; i > 0; --i) tree[i] = combine(tree[2 * i], tree[2 * i + 1]);
+    }
+    Summary query(int left, int right) const {
+        Summary before, after;
+        for (left += size, right += size; left < right; left >>= 1, right >>= 1) {
+            if (left & 1) before = combine(before, tree[left++]);
+            if (right & 1) after = combine(tree[--right], after);
+        }
+        return combine(before, after);
+    }
+};
+
+int main() {
+    ios::sync_with_stdio(false);
+    cin.tie(nullptr);
+
+    int n;
+    cin >> n;
+    struct Edge { int to; int64 weight; };
+    vector<vector<Edge>> graph(n);
+    vector<int> degree(n, 0);
+    for (int i = 0; i < n; ++i) {
+        int u, v;
+        int64 w;
+        cin >> u >> v >> w;
+        --u; --v;
+        graph[u].push_back({v, w});
+        graph[v].push_back({u, w});
+        ++degree[u]; ++degree[v];
+    }
+    queue<int> leaves;
+    vector<char> on_cycle(n, true);
+    for (int i = 0; i < n; ++i) if (degree[i] == 1) leaves.push(i);
+    while (!leaves.empty()) {
+        int v = leaves.front(); leaves.pop();
+        on_cycle[v] = false;
+        for (const Edge &edge : graph[v]) {
+            if (--degree[edge.to] == 1) leaves.push(edge.to);
+        }
+    }
+
+    vector<int64> height(n, 0);
+    vector<int> parent(n, -2);
+    int64 tree_diameter = 0;
+    for (int root = 0; root < n; ++root) {
+        if (!on_cycle[root]) continue;
+        vector<pair<int, int>> order;
+        vector<int> stack{root};
+        parent[root] = -1;
+        while (!stack.empty()) {
+            int v = stack.back(); stack.pop_back();
+            order.push_back({v, parent[v]});
+            for (const Edge &edge : graph[v]) {
+                if (on_cycle[edge.to] || edge.to == parent[v]) continue;
+                parent[edge.to] = v;
+                stack.push_back(edge.to);
+            }
+        }
+        for (int index = static_cast<int>(order.size()) - 1; index >= 0; --index) {
+            int v = order[index].first;
+            int64 first = 0, second = 0;
+            for (const Edge &edge : graph[v]) {
+                if (on_cycle[edge.to] || parent[edge.to] != v) continue;
+                int64 candidate = height[edge.to] + edge.weight;
+                if (candidate > first) { second = first; first = candidate; }
+                else if (candidate > second) second = candidate;
+            }
+            height[v] = first;
+            tree_diameter = max(tree_diameter, first + second);
+        }
+    }
+
+    int start = find(on_cycle.begin(), on_cycle.end(), true) - on_cycle.begin();
+    vector<int> cycle;
+    vector<int64> next_weight;
+    int previous = -1, current = start;
+    do {
+        cycle.push_back(current);
+        int next = -1;
+        int64 weight = 0;
+        for (const Edge &edge : graph[current]) {
+            if (on_cycle[edge.to] && edge.to != previous) {
+                next = edge.to;
+                weight = edge.weight;
+                break;
+            }
+        }
+        next_weight.push_back(weight);
+        previous = current;
+        current = next;
+    } while (current != start);
+
+    int k = cycle.size();
+    vector<int64> coordinate(2 * k, 0);
+    for (int i = 1; i < 2 * k; ++i) {
+        coordinate[i] = coordinate[i - 1] + next_weight[(i - 1) % k];
+    }
+    vector<Summary> values(2 * k);
+    for (int i = 0; i < 2 * k; ++i) {
+        int64 h = height[cycle[i % k]];
+        values[i] = {h - coordinate[i], h + coordinate[i], NEG};
+    }
+    SegmentTree segment(values);
+    int64 best_cycle_path = numeric_limits<int64>::max();
+    for (int cut = 0; cut < k; ++cut) {
+        best_cycle_path = min(best_cycle_path, segment.query(cut, cut + k).best);
+    }
+    cout << max(tree_diameter, best_cycle_path) << '\n';
+    return 0;
+}

+ 61 - 0
参考实现/10-高斯消元与CRT/AtCoder-abc366_g.cpp

@@ -0,0 +1,61 @@
+#include <bits/stdc++.h>
+using namespace std;
+
+int main() {
+    ios::sync_with_stdio(false);
+    cin.tie(nullptr);
+
+    int n, m;
+    cin >> n >> m;
+    vector<unsigned long long> row(n, 0);
+    for (int i = 0; i < m; ++i) {
+        int u, v;
+        cin >> u >> v;
+        --u; --v;
+        row[u] ^= 1ULL << v;
+        row[v] ^= 1ULL << u;
+    }
+
+    vector<int> pivot_column;
+    int rank = 0;
+    for (int column = 0; column < n; ++column) {
+        int pivot = rank;
+        while (pivot < n && ((row[pivot] >> column) & 1ULL) == 0) ++pivot;
+        if (pivot == n) continue;
+        swap(row[rank], row[pivot]);
+        for (int r = 0; r < n; ++r) {
+            if (r != rank && ((row[r] >> column) & 1ULL)) row[r] ^= row[rank];
+        }
+        pivot_column.push_back(column);
+        ++rank;
+    }
+
+    vector<char> is_pivot(n, false);
+    for (int column : pivot_column) is_pivot[column] = true;
+    vector<unsigned long long> answer(n, 0);
+    int basis_index = 0;
+    for (int free_column = 0; free_column < n; ++free_column) {
+        if (is_pivot[free_column]) continue;
+        unsigned long long vector_value = 1ULL << free_column;
+        for (int r = 0; r < rank; ++r) {
+            if ((row[r] >> free_column) & 1ULL) {
+                vector_value |= 1ULL << pivot_column[r];
+            }
+        }
+        for (int v = 0; v < n; ++v) {
+            if ((vector_value >> v) & 1ULL) answer[v] |= 1ULL << basis_index;
+        }
+        ++basis_index;
+    }
+    if (find(answer.begin(), answer.end(), 0ULL) != answer.end()) {
+        cout << "No\n";
+        return 0;
+    }
+    cout << "Yes\n";
+    for (int i = 0; i < n; ++i) {
+        if (i) cout << ' ';
+        cout << answer[i];
+    }
+    cout << '\n';
+    return 0;
+}

+ 103 - 0
参考实现/10-高斯消元与CRT/AtCoder-agc050_e.cpp

@@ -0,0 +1,103 @@
+#include <bits/stdc++.h>
+using namespace std;
+
+using int64 = long long;
+using i128 = __int128_t;
+
+constexpr int64 MOD = 998'244'353LL;
+
+int64 lcm_bounded(int64 a, int64 b) {
+    return static_cast<int64>(static_cast<i128>(a / gcd(a, b)) * b);
+}
+
+int64 prefix_short_part(int64 time, int64 period, int64 remainder) {
+    // Count integers t in [0,time) with t mod period < remainder.
+    i128 result = static_cast<i128>(time / period) * remainder
+                + min(time % period, remainder);
+    return static_cast<int64>(result % MOD);
+}
+
+int64 sum_weight(int64 left, int64 right, int64 green, int64 period) {
+    int64 base = green / period;
+    int64 remainder = green % period;
+    int64 length = (right - left) % MOD;
+    int64 extra = (prefix_short_part(right, period, remainder)
+                 - prefix_short_part(left, period, remainder) + MOD) % MOD;
+    return (base % MOD * length + extra) % MOD;
+}
+
+int64 weight_at(int64 time, int64 green, int64 period) {
+    return green / period + (time % period < green % period ? 1 : 0);
+}
+
+int64 next_change(int64 time, int64 period, int64 remainder, int64 limit) {
+    int64 next = (time / period + 1) * period;
+    if (remainder != 0) {
+        int64 block = time / period;
+        int64 shifted = block * period + remainder;
+        if (shifted <= time) ++block;
+        if (block < limit / period) shifted = block * period + remainder;
+        else shifted = limit;
+        next = min(next, shifted);
+    }
+    return min(next, limit);
+}
+
+int main() {
+    ios::sync_with_stdio(false);
+    cin.tie(nullptr);
+
+    array<int64, 3> green{}, red{}, period{};
+    for (int i = 0; i < 3; ++i) {
+        cin >> green[i] >> red[i];
+        period[i] = green[i] + red[i];
+    }
+
+    array<int64, 3> reduced{};
+    for (int i = 0; i < 3; ++i) {
+        int j = (i + 1) % 3;
+        int k = (i + 2) % 3;
+        reduced[i] = lcm_bounded(gcd(period[i], period[j]),
+                                 gcd(period[i], period[k]));
+    }
+
+    i128 reduced_lcm = reduced[0];
+    for (int i = 1; i < 3; ++i) {
+        reduced_lcm = reduced_lcm / gcd(static_cast<int64>(reduced_lcm % reduced[i]),
+                                        reduced[i]) * reduced[i];
+    }
+    int64 horizon = static_cast<int64>(reduced_lcm);
+
+    int integrated = 0;
+    for (int i = 1; i < 3; ++i) {
+        if (reduced[i] < reduced[integrated]) integrated = i;
+    }
+    if (integrated != 0) {
+        swap(green[0], green[integrated]);
+        swap(period[0], period[integrated]);
+        swap(reduced[0], reduced[integrated]);
+    }
+
+    int64 compatible_green = 0;
+    int64 current = 0;
+    while (current < horizon) {
+        int64 next = horizon;
+        for (int i = 1; i < 3; ++i) {
+            next = min(next, next_change(current, reduced[i],
+                                         green[i] % reduced[i], horizon));
+        }
+        int64 other_weights = weight_at(current, green[1], reduced[1]) % MOD;
+        other_weights = other_weights * (weight_at(current, green[2], reduced[2]) % MOD) % MOD;
+        compatible_green = (compatible_green
+                          + other_weights * sum_weight(current, next, green[0], reduced[0])) % MOD;
+        current = next;
+    }
+
+    int64 common12 = gcd(period[0], period[1]);
+    i128 lcm12 = static_cast<i128>(period[0] / common12) * period[1];
+    int64 common_with_third = gcd(static_cast<int64>(lcm12 % period[2]), period[2]);
+    int64 repetitions = common12 % MOD * (common_with_third % MOD) % MOD;
+
+    cout << compatible_green * repetitions % MOD << '\n';
+    return 0;
+}

+ 98 - 0
参考实现/10-高斯消元与CRT/CodeForces-724G.cpp

@@ -0,0 +1,98 @@
+#include <bits/stdc++.h>
+using namespace std;
+
+using ull = unsigned long long;
+constexpr long long MOD = 1'000'000'007LL;
+constexpr int BITS = 60;
+
+struct LinearBasis {
+    array<ull, BITS> pivot{};
+    int rank = 0;
+    void insert(ull value) {
+        for (int bit = BITS - 1; bit >= 0; --bit) {
+            if (((value >> bit) & 1ULL) == 0) continue;
+            if (pivot[bit] == 0) {
+                pivot[bit] = value;
+                ++rank;
+                return;
+            }
+            value ^= pivot[bit];
+        }
+    }
+    bool varies(int bit) const {
+        for (ull value : pivot) if ((value >> bit) & 1ULL) return true;
+        return false;
+    }
+};
+
+long long mod_power(long long base, int exponent) {
+    long long result = 1;
+    while (exponent > 0) {
+        if (exponent & 1) result = result * base % MOD;
+        base = base * base % MOD;
+        exponent >>= 1;
+    }
+    return result;
+}
+
+int main() {
+    ios::sync_with_stdio(false);
+    cin.tie(nullptr);
+
+    int n, m;
+    cin >> n >> m;
+    struct Edge { int to; ull weight; };
+    vector<vector<Edge>> graph(n);
+    for (int i = 0; i < m; ++i) {
+        int u, v;
+        ull w;
+        cin >> u >> v >> w;
+        --u; --v;
+        graph[u].push_back({v, w});
+        graph[v].push_back({u, w});
+    }
+
+    vector<char> seen(n, false);
+    vector<ull> potential(n, 0);
+    long long answer = 0;
+    for (int root = 0; root < n; ++root) {
+        if (seen[root]) continue;
+        vector<int> component;
+        vector<int> stack{root};
+        seen[root] = true;
+        while (!stack.empty()) {
+            int v = stack.back();
+            stack.pop_back();
+            component.push_back(v);
+            for (const Edge &edge : graph[v]) {
+                if (!seen[edge.to]) {
+                    seen[edge.to] = true;
+                    potential[edge.to] = potential[v] ^ edge.weight;
+                    stack.push_back(edge.to);
+                }
+            }
+        }
+        LinearBasis basis;
+        for (int v : component) {
+            for (const Edge &edge : graph[v]) {
+                basis.insert(potential[v] ^ potential[edge.to] ^ edge.weight);
+            }
+        }
+        long long combinations = mod_power(2, basis.rank);
+        long long pairs = 1LL * component.size() * (component.size() - 1) / 2 % MOD;
+        for (int bit = 0; bit < BITS; ++bit) {
+            long long count;
+            if (basis.varies(bit)) {
+                count = pairs * mod_power(2, basis.rank - 1) % MOD;
+            } else {
+                long long ones = 0;
+                for (int v : component) ones += (potential[v] >> bit) & 1ULL;
+                count = ones % MOD * ((component.size() - ones) % MOD) % MOD;
+                count = count * combinations % MOD;
+            }
+            answer = (answer + count * ((1ULL << bit) % MOD)) % MOD;
+        }
+    }
+    cout << answer << '\n';
+    return 0;
+}

+ 105 - 0
参考实现/11-平衡树/CodeForces-896C.cpp

@@ -0,0 +1,105 @@
+#include <bits/stdc++.h>
+using namespace std;
+
+using int64 = long long;
+
+struct Node {
+    int left;
+    mutable int right;
+    mutable int64 value;
+    bool operator<(const Node &other) const { return left < other.left; }
+};
+
+set<Node> intervals;
+int array_size;
+
+set<Node>::iterator split(int position) {
+    if (position > array_size) return intervals.end();
+    auto it = prev(intervals.upper_bound({position, 0, 0}));
+    if (it->left == position) return it;
+    int right = it->right;
+    int64 value = it->value;
+    it->right = position - 1;
+    return intervals.insert({position, right, value}).first;
+}
+
+void add_range(int left, int right, int64 value) {
+    auto end = split(right + 1);
+    for (auto it = split(left); it != end; ++it) it->value += value;
+}
+
+void assign_range(int left, int right, int64 value) {
+    auto end = split(right + 1);
+    auto begin = split(left);
+    intervals.erase(begin, end);
+    intervals.insert({left, right, value});
+}
+
+int64 kth(int left, int right, int rank) {
+    auto end = split(right + 1);
+    vector<pair<int64, int>> values;
+    for (auto it = split(left); it != end; ++it) {
+        values.push_back({it->value, it->right - it->left + 1});
+    }
+    sort(values.begin(), values.end());
+    for (auto [value, count] : values) {
+        if (rank <= count) return value;
+        rank -= count;
+    }
+    return -1;
+}
+
+int64 mod_power(int64 base, int64 exponent, int64 modulus) {
+    int64 result = 1 % modulus;
+    base %= modulus;
+    while (exponent > 0) {
+        if (exponent & 1) result = result * base % modulus;
+        base = base * base % modulus;
+        exponent >>= 1;
+    }
+    return result;
+}
+
+int64 power_sum(int left, int right, int64 exponent, int64 modulus) {
+    auto end = split(right + 1);
+    int64 result = 0;
+    for (auto it = split(left); it != end; ++it) {
+        int64 count = it->right - it->left + 1;
+        result = (result + count % modulus * mod_power(it->value, exponent, modulus)) % modulus;
+    }
+    return result;
+}
+
+int main() {
+    ios::sync_with_stdio(false);
+    cin.tie(nullptr);
+
+    int operations;
+    int64 seed, maximum;
+    cin >> array_size >> operations >> seed >> maximum;
+    auto random_value = [&]() {
+        int64 result = seed;
+        seed = (seed * 7 + 13) % 1'000'000'007LL;
+        return result;
+    };
+    for (int i = 1; i <= array_size; ++i) {
+        intervals.insert({i, i, random_value() % maximum + 1});
+    }
+    while (operations--) {
+        int type = random_value() % 4 + 1;
+        int left = random_value() % array_size + 1;
+        int right = random_value() % array_size + 1;
+        if (left > right) swap(left, right);
+        int64 x;
+        if (type == 3) x = random_value() % (right - left + 1) + 1;
+        else x = random_value() % maximum + 1;
+        if (type == 1) add_range(left, right, x);
+        else if (type == 2) assign_range(left, right, x);
+        else if (type == 3) cout << kth(left, right, static_cast<int>(x)) << '\n';
+        else {
+            int64 modulus = random_value() % maximum + 1;
+            cout << power_sum(left, right, x, modulus) << '\n';
+        }
+    }
+    return 0;
+}

+ 64 - 43
课程/00-CF2400-2700高阶扩展路线.md

@@ -1,8 +1,8 @@
-# CF 2400–2700 高阶扩展路线
+# 高阶扩展路线:Codeforces 2400–2700 与精选 AtCoder
 
 ## 扩展口径
 
-“每个课程”按 `主题.md` 的 11 个算法大主题计算;“历年真题”保持中国竞赛真题体系,不用国际平台替代。每个主题以 3 道 Codeforces 2400–2700 或思维强度相当的 AtCoder Grand Contest 题为主,只有转化彼此独立时才增至 4 道。已有详细讲解的题可计入。AGC 只在转化更典型时替换同质 CF 题,不作为降低难度或凑数量的来源。
+“每个课程”按 `主题.md` 的 11 个算法大主题计算;“历年真题”保持中国竞赛真题体系,不用国际平台替代。每个主题通常保留 3 道 Codeforces 2400–2700 或思维强度相当的 AtCoder 题,只有转化彼此独立时才增至 4 道。已有详细讲解的题计入名额。AtCoder 不是 CF 的附加题单:两者放在同一候选池内比较,优先留下考虑链更短而转化更深、能暴露算法失效边界的题。
 
 高阶题不按“用了更复杂的数据结构”入选。若去掉模板名后只剩常规分层图、常规矩阵 Lazy 或直接 CRT,则降为练习;主池优先保留需要改变对象、补图/对偶、隐式图、参数化、状态压缩或反向建模的题。
 
@@ -17,44 +17,65 @@
 7. 实现边界与去掉关键条件后的失效点;
 8. 类 Python 核心伪代码和独立 C++17 附件。
 
-## 题目矩阵
-
-| 主题 | VJudge 查询 | 难度 | 核心转化 |
-|---|---|---:|---|
-| 欧拉路径 | CodeForces-508D | 2500 | 固定窗口重建 → 后缀点、窗口边、欧拉路径 |
-| 欧拉路径 | CodeForces-547D | 2600 | 坐标奇偶约束 → 边二染/欧拉定向 |
-| 欧拉路径 | CodeForces-527E | 2600 | 奇度与边数奇偶补边 → 欧拉回路交替定向 |
-| 欧拉路径 | CodeForces-1361C | 2500 | 二进制答案单调性 → 掩码点上的欧拉回路 |
-| 强/双连通 | CodeForces-962F | 2400 | “恰属一个环” → 点双块边数等于点数 |
-| 强/双连通 | CodeForces-467D | 2400 | 单词替换闭包 → SCC 缩点 DAG 反向 DP |
-| 强/双连通 | CodeForces-1344C | 2600 | 量词可达条件 → SCC 与缩点偏序结构 |
-| Bitset/位并行 | CodeForces-1097F | 2500 | gcd 多重集 → 整除 zeta 坐标上的 AND/XOR |
-| Bitset/位并行 | CodeForces-986C | 2500 | AND 为 0 → 补集子掩码图上的隐式遍历 |
-| Bitset/位并行 | CodeForces-1523D | 2400 | 高维公共 1 → 随机抽行与子集 DP |
-| KMP/Manacher | CodeForces-825F | 2400 | 子串压缩 → KMP 周期代价 + 区间 DP |
-| KMP/Manacher | CodeForces-1827C | 2600 | 回文后缀限制 → Manacher/哈希与 DP 跳转 |
-| KMP/Manacher | CodeForces-955D | 2600 | 模式拆成两段 → 正反前缀匹配拼接 |
-| 最短路 | CodeForces-1340C | 2400 | 周期通行规则 → 位置×绿灯时间的 0-1 BFS |
-| 最短路 | CodeForces-1442C | 2400 | 翻转代价指数增长 → 次数优先的分阶段最短路 |
-| 最短路 | CodeForces-1051F | 2400 | 图近似一棵树 → LCA 距离 + 少量特殊源 Dijkstra |
-| 生成树 | CodeForces-125E | 2400 | 指定特殊边数量 → 权值加参数的 WQS/Kruskal |
-| 生成树 | CodeForces-1408E | 2400 | 禁止彩虹环 → 二分关联图上的最大生成森林补集 |
-| 生成树 | CodeForces-1508C | 2500 | 隐式完全图 → 补图连通块与候选边削减 |
-| 二分图 | CodeForces-741C | 2600 | 三连续约束 → 补固定配对边后的偶环二染 |
-| 二分图 | CodeForces-1634E | 2400 | 多重出现公平分配 → 关联二分图欧拉交替 |
-| 二分图 | CodeForces-1009G | 2400 | 字典序贪心可行性 → Hall/最大流动态检查 |
-| Segment/Fenwick | CodeForces-484E | 2500 | 区间连续高值段 → 按阈值持久化版本 |
-| Segment/Fenwick | CodeForces-786C | 2400 | 对所有容量求最少分段 → 大小参数分治计算 |
-| Segment/Fenwick | CodeForces-1439C | 2600 | 单调区间购买 → 最大值剪枝与批量覆盖 |
-| Segment/Fenwick | CodeForces-1172F | 3300(灵活性) | 区间视为函数 → 合成函数摘要而非数值摘要 |
-| 基环树 | CodeForces-835F | 2500 | 基环树直径 → 挂树直径 + 环上单调队列 |
-| 基环树 | CodeForces-1270G | 2700 | 零和子集构造 → 函数图中取一个有向环 |
-| 基环树 | CodeForces-875F | 2500 | 最大权可选边 → 伪森林独立性上的 Kruskal 贪心 |
-| 高斯/CRT | CodeForces-724G | 2600 | 图上 XOR 路径全集 → 生成树势能与环线性基 |
-| 高斯/CRT | CodeForces-906D | 2700 | 非互质幂塔取模 → Euler 链与“大数标记” |
-| 高斯/CRT | CodeForces-959F | 2400 | 前缀 XOR 表示方案数 → 增量线性基与自由维数 |
-| 平衡树/序列结构 | CodeForces-455D | 2700 | 在线循环移位与频次 → 分块/隐式树结构选择 |
-| 平衡树/序列结构 | CodeForces-702F | 2800(灵活性) | 购买过程按能力分组 → 带 Lazy 的按值 Treap |
-| 平衡树/序列结构 | CodeForces-896C | 2600 | 随机区间操作 → 有序区间树的 split/assign |
-
-其中平衡树组会明确比较 FHQ/Splay、分块、ODT、线段树等接口:重点不是强迫所有题使用平衡树,而是学习从操作集合判断哪种动态序列结构真正合适。
+## 正式题目矩阵
+
+表中“已有”表示原课程已经完整讲解;“新增”表示本轮进入对应课程的正式高阶扩展。未进入本表的 `problems/4` 题目不是被忽略,而是在后面的取舍表中说明降级原因。
+
+| 主题 | 状态 | VJudge 查询 | 难度口径 | 独立思维任务 |
+|---|---|---|---:|---|
+| 欧拉路径 | 已有 | CodeForces-508D | 2500 | 固定窗口重建 → 后缀点、窗口边、欧拉路径 |
+| 欧拉路径 | 新增 | CodeForces-1361C | 2500 | 二进制答案单调性 → 掩码点上的欧拉回路 |
+| 欧拉路径 | 新增 | AtCoder-agc018_f | AGC 1700 分 | 两棵树的子树和 → 偶度辅助图上的欧拉定向 |
+| 欧拉路径 | 新增 | AtCoder-agc032_c | AGC 800 分 | “恰分成三条回路” → 压缩二度点后的拓扑分类 |
+| 强/双连通 | 已有 | CodeForces-962F | 2400 | “恰属一个环” → 点双块边数等于点数 |
+| 强/双连通 | 新增 | AtCoder-agc071_c | AGC 1100 分 | 任意目标度数 → 二分性、点双块与完备子集和 |
+| Bitset/位并行 | 已有 | CodeForces-1097F | 2500 | gcd 多重集 → 整除 zeta 坐标上的 AND/XOR |
+| Bitset/位并行 | 新增 | CodeForces-986C | 2500 | AND 为零 → 补集子掩码图的隐式遍历 |
+| KMP/Manacher | 已有 | CodeForces-17E | 2400 | 回文对计数 → 半径区间的差分扫描 |
+| KMP/Manacher | 新增 | AtCoder-qupc2018_h | 同强度构造题 | 给定回文半径反造字符串 → 等/不等约束图 |
+| 最短路/类最短路 | 新增 | CodeForces-1340C | 2400 | 周期通行规则 → 位置乘绿灯时间的 0-1 BFS |
+| 最短路/类最短路 | 新增 | AtCoder-agc056_c | AGC 900 分 | 平衡区间 → 前缀势能差分约束与 0-1 最短路 |
+| 最短路/类最短路 | 灵活性 | AtCoder-agc062_d | AGC 1100 分 | 二维行走 → 只保留离原点距离与部分和可达区间 |
+| 生成树 | 已有 | CodeForces-888G | 2600 | 完全图 XOR MST → 位分治与跨割最小异或 |
+| 生成树 | 新增 | CodeForces-1408E | 2400 | 禁止彩虹环 → 二分关联图最大生成森林的补集 |
+| 生成树 | 新增 | AtCoder-agc014_e | AGC 1400 分 | 两棵树的操作序 → 同色平行边驱动的同步缩点 |
+| 二分图 | 已有 | CodeForces-741C | 2600 | 三连续约束 → 补固定配对边后的偶环二染 |
+| 二分图 | 新增 | CodeForces-1634E | 2400 | 公平分配 → 值与序列关联图的欧拉交替 |
+| 二分图 | 新增 | AtCoder-agc025_d | AGC 800 分 | 两个禁距图 → 两次二染交叉成四个独立类 |
+| Segment/Fenwick | 新增 | AtCoder-agc023_e | AGC 1700 分 | 受限排列总逆序数 → 交换配对计数与带零因子 BIT |
+| Segment/Fenwick | 灵活性 | CodeForces-1172F | 3300 | 区间视为函数 → 合成函数摘要而非数值摘要 |
+| 基环树/函数图 | 新增 | CodeForces-835F | 2500 | 基环树直径 → 挂树直径与断环窗口的有序摘要 |
+| 基环树/函数图 | 新增 | CodeForces-1270G | 2700 | 零和子集构造 → 函数图中抽取有向环 |
+| 基环树/函数图 | 灵活性 | AtCoder-agc062_f | AGC 2000 分 | 牌堆阻塞 → 动态函数图剥叶与交错环 |
+| 高斯/CRT | 新增 | CodeForces-724G | 2600 | 图上 XOR 路径全集 → 树势能与环空间 |
+| 高斯/CRT | 新增 | AtCoder-abc366_g | ABC 600 分 | 邻点 XOR 条件 → 邻接矩阵零空间覆盖 |
+| 高斯/CRT | 新增 | AtCoder-agc050_e | AGC 1800 分 | 三周期相交 → 非互质 CRT、素因子指数削减与事件扫描 |
+| 平衡树/序列结构 | 已有 | CSES-2072/2073/2074 | 课程主线 | split/merge、翻转与剪贴接口 |
+| 平衡树/序列结构 | 新增 | CodeForces-896C | 2600 | 随机数据契约 → 有序区间树及其失效边界 |
+
+其中平衡树组不为凑数保留 `455D`、`702F`:前者本质更接近分块,后者虽能用按值 Treap,但核心考虑链与本课程主线连接较弱。该组用 `896C` 专门讨论“输入随机”何时是算法正确性/复杂度契约,并明确 ODT 不是平衡树的替代模板。
+
+## `problems/4` 中降为训练或旁支的题
+
+| 题目 | 归档位置 | 不占正式名额的原因 |
+|---|---|---|
+| CodeForces-527E | 欧拉路径课后训练 | 奇度补边、边数补偶再交替定向很完整,但核心仍是标准欧拉修补 |
+| CodeForces-547D | 欧拉路径课后训练 | 坐标点建边很漂亮,但与 `1634E` 的“二分关联图 + 欧拉交替”任务重叠 |
+| CodeForces-467D | SCC 课后训练 | SCC 缩点后做 DAG DP,适合巩固,不足以占高阶转化名额 |
+| CodeForces-825F | KMP 课后训练 | 周期代价加区间 DP 是良好组合题,但建模较直接 |
+| CodeForces-955D | KMP 课后训练 | 正反匹配拆成两段值得练习,但考虑链短于正式两题 |
+| CodeForces-1051F | 最短路课后训练 | “树加少量非树边”后从特殊点跑 Dijkstra 是常用降维套路 |
+| CodeForces-125E | 生成树选学 | WQS/Kruskal 有价值,但更偏参数化模板,本轮让位给补图和同步缩点 |
+| AtCoder-agc064_b | 生成树衔接题 | 森林先满足局部颜色、再补成树很清楚,适合作为正式高阶题前的构造台阶 |
+| CodeForces-959F | 高斯消元课后训练 | 增量线性基加自由维计数较标准,让位给“零空间覆盖” |
+| CodeForces-1344C | SCC 二次筛选池 | 量词转偏序很强,但本轮与点双块的完整闭环相比,尚未完成可对拍实现,不标“新增” |
+| CodeForces-1523D | Bitset 二次筛选池 | 随机抽行 + 子集 zeta 有价值,但随机正确率契约需单独成课,先保留 `986C` 的确定性隐式图转化 |
+| CodeForces-1827C | Manacher 二次筛选池 | 题目很强,但与 `qupc2018_h` 同轮加入会让回文半径主题跨度过大,待补中间台阶 |
+| CodeForces-1442C | 最短路二次筛选池 | 指数代价的分阶段序巧妙;本轮保留状态压缩更能直接承接主线的 `1340C` |
+| CodeForces-1508C | 生成树二次筛选池 | 补图候选边削减质量高,但与 `1408E`、`agc014_e` 同时正式化会超过主题默认题量 |
+| CodeForces-1009G | 二分图二次筛选池 | 动态 Hall 条件实现门槛高且依赖前置不足,不能用结论式题解硬塞 |
+| CodeForces-1439C | Segment 课后选学 | 数据结构实现精彩,但核心更接近单调段批量覆盖;本轮让位给交换对合驱动的 `agc023_e` |
+| CodeForces-875F | 函数图二次筛选池 | 伪森林独立性上的 Kruskal 值得保留,但与 `835F` 的环上最优化、`1270G` 的代数构造相比独立收获略弱 |
+| CodeForces-906D | CRT 二次筛选池 | 非互质幂塔需要 Euler 链与“大数标记”完整前置;本轮 `agc050_e` 已承担非互质周期压缩任务 |
+
+`AtCoder-agc062_d` 与 `AtCoder-agc062_f` 都保留,但明确标为灵活性实验室:它们的价值分别是“从二维运动删到一维半径状态”和“从操作过程反建动态函数图”,不作为后续章节硬前置。

+ 134 - 0
课程/01-欧拉路径/01-判定构造与建模.md

@@ -370,6 +370,140 @@ discard dummy edges and output recorded original orientations
 
 本题最重要的不是“会欧拉”,而是先用奇偶性写出答案上界,再用欧拉回路让所有可行点同时达到上界。若目标改成让 `|out-in|≤1` 的点最多,奇度点也可能合格,上界与补边方式都会改变。
 
+## 高阶扩展 1:Two Trees——把两套子树约束拼成一张欧拉图
+
+### 来源信息、题意与范围
+
+- VJudge:[AtCoder-agc018_f Two Trees](https://vjudge.net/problem/AtCoder-agc018_f)
+- 原题:[AtCoder Grand Contest 018 F](https://atcoder.jp/contests/agc018/tasks/agc018_f)
+- 官方难度口径:1700 分;主要考点:子树和奇偶、辅助图、欧拉回路定向
+
+给出顶点编号相同的两棵 $N$ 点根树。构造整数 $X_1,ldots,X_N$,使两棵树中每个顶点的整棵子树之和的绝对值都为 $1$;无解时报告。$1\le N\le10^5$,两行父亲数组各恰描述一棵合法根树。
+
+<details><summary>Hint 1:先只看模 2</summary>
+
+子树和是 $\pm1$,模 2 都等于 $1$。若 $v$ 有 $c_v$ 个孩子,把孩子子树的奇数和加起来,便能确定 $X_v$ 的奇偶。
+
+</details>
+
+<details><summary>Hint 2:两棵树何时立即矛盾</summary>
+
+第一棵树给出 $X_v\equiv 1-c_v\pmod2$,第二棵树给出 $X_v\equiv1-d_v\pmod2$。所以所有 $c_v,d_v$ 必须同奇偶。
+
+</details>
+
+<details><summary>Hint 3:为什么答案只需 $0,\pm1$</summary>
+
+为两棵树各复制一层顶点。奇数型 $X_v$ 用一条跨层边连接两份 $v$;树边和两条“根到公共点”的边补齐各点度数。若所有度数为偶数,就能沿欧拉回路给跨层边定向。
+
+</details>
+
+### Observations
+
+1. 直接同时做两棵树形 DP,会让同一个 $X_v$ 同时受两套祖先关系约束,状态没有局部闭合性;模 2 后,每棵树只留下“孩子数奇偶”这一条必要条件。
+2. 条件通过后,不必搜索任意整数。令偶数型点取 $0$、奇数型点取 $\pm1$,问题变成让每棵树每个子树内的符号和为 $\pm1$。
+3. “每个子树只剩一个未抵消符号”可由流量守恒表达:欧拉定向令辅助图每点入度等于出度,跨过一棵子树边界的净流恰为 $\pm1$。
+
+### Editorial、证明与复杂度
+
+设第一、二棵树中 $v$ 的孩子数分别为 $c_v,d_v$。对子树和取模 2 得
+
+$$
+X_v+c_v\equiv1\pmod2,
+\qquad
+X_v+d_v\equiv1\pmod2.
+$$
+
+所以 $c_v\not\equiv d_v\pmod2$ 时无解。
+
+否则建立 $2N+1$ 点辅助图:第一层放第一棵树的全部无向树边,第二层放第二棵树的树边;两棵树的根分别连公共点 $R$。当 $c_v$ 为偶数时,再连跨层边 $P_v-Q_v$。对 $P_v$ 而言,树边加根边共贡献 $c_v+1$ 度:$c_v$ 偶时再加跨层边,$c_v$ 奇时不加,两种情况度数都为偶数;第二层同理。辅助图连通且全点偶度,故存在欧拉回路。
+
+沿回路方向定向所有边。跨层边 $P_v\to Q_v$ 时令 $X_v=1$,反向令 $X_v=-1$;无跨层边令 $X_v=0$。在第一层对任一子树内顶点做“入度 = 出度”求和,内部树边抵消,只剩子树根通向父亲/公共点的一条边与跨层边净流,因此子树内 $X$ 之和为 $\pm1$;第二层完全相同。这同时证明合法性和完备性。建图与 Hierholzer 都是 $O(N)$ 时间、$O(N)$ 空间。
+
+### 核心伪代码、实现边界与附件
+
+```text
+if parity(child_count_tree1[v]) differs from tree2 for some v:
+    report IMPOSSIBLE
+build two tree layers P and Q plus common vertex R
+connect both roots to R
+for each v with even child count: add cross edge P[v] -- Q[v]
+tour = Euler_circuit(auxiliary_graph)
+for each v:
+    X[v] = 0 if there is no cross edge
+    otherwise X[v] = +1 or -1 according to its traversal direction
+```
+
+父亲数组含唯一的 `-1` 根;辅助图的无向边必须共享编号。实现采用迭代 Hierholzer,避免 $N=10^5$ 的递归深度。完整 GNU C++17 附件:[AtCoder-agc018_f.cpp](../../参考实现/01-欧拉路径/AtCoder-agc018_f.cpp)
+
+### 复盘与变式
+
+识别信号不是“两棵树”,而是“每个层次集合的和只能为 $\pm1$”。若目标绝对值改成任意 $K>1$,模 2 只给必要条件,单条跨层边也不再足以表达边界净流,本构造失效。
+
+## 高阶扩展 2:Three Circuits——从构造回路转向压缩拓扑
+
+### 来源信息、题意与范围
+
+- VJudge:[AtCoder-agc032_c Three Circuits](https://vjudge.net/problem/AtCoder-agc032_c)
+- 原题:[AtCoder Grand Contest 032 C](https://atcoder.jp/contests/agc032/tasks/agc032_c)
+- 官方难度口径:800 分;主要考点:欧拉图必要条件、二度点压缩、回路空间拓扑分类
+
+给定简单连通无向图,问能否把全部边不重不漏地分成恰好三条 circuit;circuit 允许重复顶点但不允许重复边。$1\le N,M\le10^5$。
+
+<details><summary>Hint 1:三条闭合行走给度数什么条件</summary>
+
+每条 circuit 在每个点贡献偶数度,三者并仍要求原图所有点度数为偶数。
+
+</details>
+
+<details><summary>Hint 2:为什么只需关注度数至少 4 的点</summary>
+
+度为 2 的点在任何分解中只能把两条关联边接成同一段,可把整条二度链压成一条“拓扑边”。
+
+</details>
+
+<details><summary>Hint 3:只有两个四度点时还差什么</summary>
+
+设它们为 $A,B$。若存在一条完全避开 $B$、从 $A$ 回到 $A$ 的回路,就可把它与剩余结构拆成三条;否则压缩后只是四条 $A$ 到 $B$ 的链,只能两两配成两条 circuit。
+
+</details>
+
+### Observations
+
+1. 先枚举三条回路会面对指数级边划分;度数奇偶直接排除所有非欧拉图。
+2. 某点度至少 6 时,一条从该点开始的欧拉回路可按每次回到该点切成至少三段,删掉了全局划分搜索。
+3. 若至少三个点度为 4,即使其余全为 2,压缩图也有足够的分叉把回路空间拆成三份;真正临界情形只有恰两个四度点。
+
+### Editorial、证明与复杂度
+
+先检查所有度为偶数。若有点度至少 6,欧拉回路在该点产生至少三段闭合子行走,可合并多余段得到恰三条。以下最高度数为 4。若四度点至少三个,沿欧拉回路观察它们的重复出现,可以在三个分叉处重新配对边段得到三条 circuit。
+
+若四度点少于两个,压缩全部二度链后的连通偶图至多只有一个分叉,不可能有三条非空 circuit。恰有两个四度点 $A,B$ 时,删除 $B$ 后检查 $A$ 是否仍位于某个环上:若是,该环作为第一条,余下偶度边含连接 $A,B$ 的两组边段,可分成另两条;若否,所有从 $A$ 出发的四条压缩边都通向 $B$,任一 circuit 必须消耗两条 $A-B$ 链,最多只能得到两条。一次 DFS 即可判定,时间 $O(N+M)$、空间 $O(N+M)$。
+
+### 核心伪代码、实现边界与附件
+
+```text
+if some degree is odd: No
+if some degree >= 6: Yes
+four = vertices with degree 4
+if len(four) >= 3: Yes
+if len(four) <= 1: No
+A, B = four
+if DFS can leave A and return to A without visiting B: Yes
+else: No
+```
+
+简单图条件让 DFS 中按父点跳过返边足够;若允许重边,必须改用父边编号,否则两条平行边组成的环会被漏掉。完整 GNU C++17 附件:[AtCoder-agc032_c.cpp](../../参考实现/01-欧拉路径/AtCoder-agc032_c.cpp)
+
+### 复盘与变式
+
+这题的迁移点是“先压缩所有行为被迫的二度点,再分类少数分叉点”。若题目要求输出三条 circuit,判定结论仍可用,但还需保存压缩链并在欧拉序上落实切分点。
+
+## 高阶课后训练:两道降级 CF
+
+- [CodeForces-547D Mike and Fish](https://vjudge.net/problem/CodeForces-547D):把每个输入点看成连接其横坐标点与纵坐标点的边,欧拉交替染边平衡每个坐标上的两色数量。它和二分图章节的 `Fair Share` 共用考虑链,放在这里做迁移训练。
+- [CodeForces-527E Data Center Drama](https://vjudge.net/problem/CodeForces-527E):补齐奇度点,再在需要时补一条边令边数为偶数,最后沿欧拉回路交替定向。重点复盘每一种补边分别修复哪个不变量。
+
 ## 扩展与联系
 
 - **de Bruijn 图**:固定长度窗口重建、DNA 片段拼接等问题都可使用“前后缀为点、片段为边”。真实数据有错误时,问题会从严格欧拉转向路径覆盖或纠错,不再能直接套本节算法。

+ 93 - 0
课程/01-欧拉路径/02-高阶扩展-掩码图上的欧拉回路.md

@@ -0,0 +1,93 @@
+# 高阶扩展:掩码图上的欧拉回路
+
+## 本节目标
+
+从“最大化相邻连接的二进制低位相等数”推导出答案单调性,再把固定物件看成边、可粘合颜色看成点,用欧拉回路同时完成判定与构造。
+
+## 前置知识与课前自测
+
+前置:无向欧拉回路、Hierholzer、位运算。
+
+1. 两数 XOR 能被 $2^k$ 整除等价于哪 $k$ 位相同?
+2. 若每个物件必须整体使用且可翻转,为什么它更像边而不是点?
+3. 无向多重图存在覆盖全部边的欧拉回路需要什么条件?
+
+<details><summary>答案</summary>
+
+1. 最低 $k$ 位。
+2. 物件有两个可交换端点,使用一次正好对应沿一条无向边走一次。
+3. 所有非零度点连通且度数为偶数。
+
+</details>
+
+## 问题动机:先固定目标值
+
+若直接排列 $n$ 个链节并决定每个链节朝向,搜索空间包含排列与 $2^n$ 次翻转。真正可利用的不是“美丽值”的数值,而是阈值判定:要求每次粘合美丽值至少为 $k$,等价于粘合的两种颜色最低 $k$ 位相同。
+
+随着 $k$ 增大,条件只会变严。因此先解决“给定 $k$ 能否构造”,再从 20 向下找第一个可行值。
+
+## 精选例题:Johnny and Megan's Necklace
+
+### 来源与题意
+
+- VJudge:[CodeForces-1361C](https://vjudge.net/problem/CodeForces-1361C)
+- 原题:[Codeforces 1361C](https://codeforces.com/problemset/problem/1361/C)
+- 难度:2500;考点:二进制阈值、掩码多重图、欧拉构造
+
+有 $n\le5\times10^5$ 个链节,第 $i$ 个两端颜色为 $a_i,b_i<2^{20}$。链节可翻转,并把所有端点两两粘合成一个环。一次粘合的美丽值是 $a\oplus b$ 的二进制末尾零数(相等时为 20);最大化所有粘合中的最小值,并输出链上 $2n$ 个原端点编号顺序。
+
+<details><summary>Hint 1</summary>固定 $k$ 后只保留颜色 `color & ((1&lt;&lt;k)-1)`。</details>
+<details><summary>Hint 2</summary>一个链节连接两个掩码,应成为无向多重图的一条边。</details>
+<details><summary>Hint 3</summary>链节内部对应走过一条边;两个相邻链节的粘合处对应欧拉游走停在同一顶点。</details>
+<details><summary>Hint 4</summary>从 20 降序检查;第一个欧拉图直接用 Hierholzer 恢复每条边的方向。</details>
+
+### Observations
+
+1. 只检查每个低位颜色出现偶数次还不够:两个互不相连的欧拉分量无法粘成一条项链。连通性与偶度缺一不可。
+2. 固定 $k$ 后,原链节 $i$ 是边 $(a_i\bmod2^k,b_i\bmod2^k)$。链节可翻转正好对应无向边可沿任一方向经过。
+3. 一条覆盖全部边的欧拉回路给出边序。相邻边共享的欧拉顶点意味着对应端点低 $k$ 位相同,因而粘合美丽值至少为 $k$。
+4. 若某项链达到阈值 $k$,沿链节内部看边、沿粘合处看公共顶点,它反过来就是该掩码图的欧拉回路。因此模型不是仅充分,而是充要。
+
+### Editorial、证明与复杂度
+
+对 $k=20,19,\ldots,0$,建立 $2^k$ 个掩码点和 $n$ 条链节边。检查所有度数为偶数,再从任一非零度点运行 Hierholzer;若最终使用边数少于 $n$,说明非零度部分不连通。
+
+可行时,把欧拉回路中的有向经过记为 `from --edge i--> to`。若 `a_i` 的掩码等于 `from`,输出端点 `2i-1,2i`;否则反向输出。这样每一对输出位置是同一链节,而前一链节的末端与后一链节的首端都代表同一欧拉顶点。回路首尾同理。
+
+上述双向对应证明判定充要;降序枚举的首个可行 $k$ 即最优值。每轮 $O(n+2^k)$,几何级数 $\sum2^k=O(2^{20})$,总时间 $O(20n+2^{21})$,空间 $O(n+2^{20})$。
+
+### 实现指南、核心伪代码与附件
+
+```text
+for k from 20 down to 0:
+    mask = 2^k - 1
+    for each part i:
+        add edge i between a[i]&mask and b[i]&mask
+    if every degree is even:
+        tour = Hierholzer from a nonzero-degree vertex
+        if tour uses all n edges:
+            orient every part by its traversal direction
+            output k and the corresponding endpoint pairs
+            stop
+```
+
+[完整 GNU C++17 实现](../../参考实现/01-欧拉路径/CodeForces-1361C.cpp)
+
+## 扩展与联系
+
+这一建模可推广到“固定二端物件必须全部串成一个环,接口只按等价类兼容”的问题:接口类作点、物件作边。若只需若干个环,连通性条件可删除,但答案会变成每个欧拉分量各造一环。
+
+### 实现细节与失效边界
+
+- 必须按边编号判重;自环会在同一点邻接表出现两次,但只能使用一次。
+- `k=0` 时只有一个掩码点,保证兜底可行。
+- Hierholzer 得到的边常为逆序退出序,输出前应整体反转,不能只翻转每条边。
+- 若目标改成最大化粘合美丽值之和,阈值单调性消失,欧拉可行性不再直接给最优。
+
+## 总结与自检
+
+请独立证明“可行项链 $\Leftrightarrow$ 掩码图欧拉回路”,并手算一个偶度但不连通的反例。再回答:为什么从大到小枚举一定能找到最优阈值?
+
+## 课后训练建议
+
+先只输出最优 $k$,确认判定后再恢复端点方向;卡在构造 25 分钟后只看 Hint 3。完成后用随机小数据验证输出的端点恰好使用一次、链节成对且所有粘合低位相同。

+ 72 - 0
课程/02-强连通与双连通/03-割点点双连通与圆方树入口.md

@@ -139,6 +139,78 @@ sort and output answer edge IDs
 
 识别信号是“边属于多少个简单环”,它比单纯判桥更细,需要看边所在点双块的整体结构。若图允许重边,两个点两条平行边是否按题目定义构成长度 2 环必须先确认,不能直接沿用简单图结论。
 
+## 高阶扩展:Orientable as Desired——点双块控制局部自由度
+
+### 来源信息、题意与范围
+
+- VJudge:[AtCoder-agc071_c Orientable as Desired](https://vjudge.net/problem/AtCoder-agc071_c)
+- 原题:[AtCoder Grand Contest 071 C](https://atcoder.jp/contests/agc071/tasks/agc071_c)
+- 官方难度口径:1100 分;考点:二分图、边栈点双分解、完备子集和判定
+
+对每个简单连通无向图 $G$,问是否存在 $0\le X_v\le\deg(v)$,使得**无论**怎样给边定向,都至少有一个顶点 $v$ 同时不满足 $\operatorname{in}(v)=X_v$ 与 $\operatorname{out}(v)=X_v$。测试总 $\sum N,\sum M\le2\times10^5$。
+
+量词顺序必须写清:我们先选整组 $X$,对手再尝试找一种方向;题目询问能否让对手对所有方向都失败。
+
+<details><summary>Hint 1:先试 X 全为 0</summary>
+
+若图含奇环,不可能把每个点都变成“入度为 0 或出度为 0”:那等价于用源点/汇点二染奇环。因此非二分图立即存在一组坏死所有定向的 $X$。
+
+</details>
+
+<details><summary>Hint 2:固定一个点,只改变 X_v</summary>
+
+令其余 $X_u=0$。同一个点双块中与 $v$ 相接的边,在源/汇交替定向里必须整组选择同一侧;不同点双块才可独立翻转。
+
+</details>
+
+<details><summary>Hint 3:哪些组大小能拼出所有数</summary>
+
+把 $v$ 在各点双块中的关联边数排序为 $c_1\le\cdots\le c_k$。从可达区间 $[0,S]$ 出发,下一组不留下空洞当且仅当 $c_i\le S+1$。
+
+</details>
+
+### Observations
+
+1. 枚举 $X$ 有 $\prod_v(\deg(v)+1)$ 种,枚举方向有 $2^M$ 种;先用 $X=0$ 试探,量词博弈立刻暴露二分性。
+2. 桥不是“特殊失败边”,而是只含一条关联边的点双块,给固定顶点贡献组大小 $1$;这使树上总能逐点选择方向。
+3. 点双块的作用不是判环本身,而是刻画“在顶点 $v$ 看来哪些关联边无法独立翻转”。分解后,局部可实现入度集合恰是组大小的子集和。
+
+### Editorial 与正确性证明
+
+官方结论的反面更容易判定:**不存在**题目所求 $X$,当且仅当:
+
+1. $G$ 是二分图;
+2. 对每个顶点 $v$,其各点双块内关联边数 $c_1,ldots,c_k$ 能用子集和表示 $0$ 到 $\deg(v)$ 的每个整数。
+
+必要性:若图有奇环,取 $X=0$ 即可让所有定向失败。若固定 $v$ 的组大小无法拼出某个 $x$,取 $X_v=x$、其余为 0;其余点要满足 0,就必须成为源或汇。在一个点双块里,沿偶环传播后,$v$ 的同组边只能整组入或整组出,于是 $x$ 不可实现。
+
+充分性:先看树。根到叶处理,每个点只有父边方向已定,其余子边可补出任意要求的入度或出度。一般图的块割树仍是一棵树;把一个点双块视为树上的“粗边”。原图二分保证块内可按两侧统一源汇定向,而条件 2 保证在割点处能选若干整组凑出所需数量,于是对任意 $X$ 都能逐块构造一种满足方向,题目所求的反例 $X$ 不存在。
+
+排序后,组大小能覆盖所有 $[0,\sum c_i]$ 的充要条件是:初始 $S=0$,每次有 $c_i\le S+1$,再令 $S\leftarrow S+c_i$。若 $c_i>S+1$,数 $S+1$ 无法表示;反之新区间 $[0,S]$ 与 $[c_i,c_i+S]$ 无缝相接。点双分解 $O(N+M)$,所有组排序总计 $O(M\log M)$。
+
+### 实现指南、伪代码与附件
+
+```text
+bipartite = two_color(G)
+blocks = vertex_biconnected_edge_blocks(G)  // Tarjan edge stack
+for each block:
+    count how many popped edges touch each incident vertex v
+    append that count to groups[v]
+complete = True
+for each vertex v:
+    sort(groups[v]); covered = 0
+    for c in groups[v]:
+        if c > covered + 1: complete = False
+        covered += c
+answer = not (bipartite and complete)
+```
+
+点双块中的 $c$ 是**关联边数**而非块的顶点数。连通简单图仍应按边编号维护父边;附件用迭代 DFS,避免链图深度 $2\times10^5$ 爆栈。完整 GNU C++17 实现:[AtCoder-agc071_c.cpp](../../参考实现/02-强连通与双连通/AtCoder-agc071_c.cpp)
+
+### 复盘与变式
+
+识别信号是“方向选择在环中传播、但在割点处分组独立”。若要求实际输出那组 $X$,在发现奇环时可取全 0;在覆盖断点 $S+1$ 时令该点取 $S+1$、其余取 0。若图允许重边,二边环也属于点双结构,父边必须按边号跳过。
+
 ## 扩展与联系
 
 - 圆方树把一个割点在多个点双块中的“重叠”变成树上的多条边,适合必经点计数与路径查询。

+ 102 - 0
课程/03-Bitset/02-高阶扩展-补集子掩码隐式图.md

@@ -0,0 +1,102 @@
+# 高阶扩展:补集子掩码构成的隐式图
+
+## 本节目标
+
+把“按位 AND 为零”的稠密图邻接关系改写成补集的全部子掩码,并用共享的子掩码状态层把潜在 $O(m2^n)$ 枚举压到 $O(n2^n)$。
+
+## 前置知识与课前自测
+
+前置:位掩码、子掩码、连通分量 DFS/BFS。
+
+1. $x\&y=0$ 等价于 $y$ 是哪个掩码的子掩码?
+2. 为每个 $x$ 枚举其全部邻点,最坏会重复访问一个掩码多少次?
+3. 若一个辅助状态已在某连通分量访问过,另一分量还可能通过它发现新点吗?
+
+<details><summary>答案</summary>
+
+1. `FULL xor x` 的子掩码。
+2. 指数次。
+3. 不可能;能由同一辅助状态到达的所有真实点已经与该分量连通。
+
+</details>
+
+## 问题动机
+
+原图的邻接判定是 $O(1)$,却可能有平方级边;本节关注的不是加速一次 AND,而是怎样让许多真实点共享一次“枚举某个下闭包”的工作。
+
+## 精选例题:AND Graph
+
+### 来源、题意与动机
+
+- VJudge:[CodeForces-986C](https://vjudge.net/problem/CodeForces-986C)
+- 原题:[Codeforces 986C](https://codeforces.com/problemset/problem/986/C)
+- 难度:2500;考点:补集、子掩码 DAG、隐式连通分量
+
+给出 $m$ 个互异的 $n$ 位数,$0\le n\le22$、$m\le2^n$。若 $x\&y=0$ 就连无向边,求连通分量数。
+
+显式图可能有 $\Theta(m^2)$ 条边;即使对每个点枚举补集子掩码,总工作也可能接近 $3^n$。
+
+<details><summary>Hint 1</summary>从真实点 $x$ 跳到辅助状态 `FULL xor x`。</details>
+<details><summary>Hint 2</summary>辅助状态 $s$ 可向 `s without one set bit` 走;这样恰遍历 $s$ 的全部子掩码。</details>
+<details><summary>Hint 3</summary>当辅助状态 $s$ 本身是输入值时,再跳回真实点 $s$,继续展开它的补集。</details>
+<details><summary>Hint 4</summary>真实点与辅助掩码用两套访问标记;每个辅助掩码全局只展开一次。</details>
+
+## Observations 与 Editorial
+
+建立两个逻辑层:
+
+- `real(x)`:只对输入集合中的数存在,代表原图顶点;
+- `mask(s)`:对所有 $0..2^n-1$ 存在,可向删掉任意一个 1 后的状态走。
+
+从 `real(x)` 进入 `mask(FULL xor x)`。沿删位边能且只能到达它的全部子掩码 $y$,这些正是满足 $x\&y=0$ 的候选;若 $y$ 在输入集合,就进入 `real(y)`。所以一次交替的 `real -> mask downset -> real` 精确模拟原图的一条边,反复交替精确模拟原图路径。
+
+从每个尚未访问的真实点启动一次搜索并计数。辅助层虽然使用有向删位边,但只承担“批量枚举下闭包”;原图的对称性由每个真实点都能再次进入自己补集保证。
+
+### 正确性证明
+
+- **合法性:** 从 `real(x)` 到达的任何真实点 $y$ 都是 `FULL xor x` 的子掩码,因此 $x\&y=0$,对应原图边;拼接得到的搜索路径都是原图路径。
+- **完备性:** 对任意原图边 $x-y$,$y\subseteq FULL\oplus x$,删位链必能从该补集到 $y$,所以搜索不会漏掉相邻点,归纳后不漏同分量点。
+- **全局剪枝:** 若 `mask(s)` 已从某分量展开,它可到达的全部真实点都已并入该分量。后来再次到达 $s$ 就已经证明两个入口连通,无需重扫其下闭包。
+
+每个真实点访问一次;每个辅助掩码访问一次并枚举至多 $n$ 个删位转移。时间 $O(n2^n+m)$,空间 $O(2^n)$,适合 $n\le22$。
+
+## 实现指南、核心伪代码与附件
+
+```text
+explore(real x):
+    if real x visited: return
+    mark real x
+    explore_mask(FULL xor x)
+
+explore_mask(s):
+    if mask s visited: return
+    mark mask s
+    if s is an input value: explore(real s)
+    for each set bit b of s:
+        explore_mask(s xor b)
+
+for each input value x not visited:
+    components += 1
+    explore(real x)
+```
+
+[完整 GNU C++17 实现](../../参考实现/03-Bitset/CodeForces-986C.cpp)
+
+## 扩展与联系
+
+辅助子掩码层本质是布尔格上的有向无环图;SOS DP 也在同一格上聚合信息,但这里做的是可达性与连通分量,而不是数值求和。
+
+## 实现、失效边界
+
+- 两层必须分开标记:辅助掩码可能不是原图顶点。
+- 递归深度虽然单次删位不超过 22,但真实层交替可很长;附件用显式栈避免调用栈风险。
+- 若邻接条件改成 `popcount(x&y)<=1`,一个补集下闭包不再精确描述邻居,需要增加缺失位状态或换模型。
+- 解释为什么复杂度是 $n2^n$ 而不是 $3^n$;再画出 $n=3$、输入 `{1,2,6}` 的两层搜索。
+
+## 总结与自检
+
+为什么需要真实层与掩码层两套标记?全局跳过已访问辅助状态为什么不会把两个分量错误合并?
+
+## 课后训练建议
+
+先写显式 $O(m^2)$ 暴力作为随机对拍,再实现两层图。卡 20 分钟后看 Hint 2,先不要看完整伪代码。

+ 70 - 0
课程/04-KMP与Manacher/02-Manacher回文半径与区间计数.md

@@ -138,6 +138,76 @@ output choose(P, 2) - disjoint  (mod MOD)
 
 若题目问相交回文对的交集长度总和,简单补集计数不再够用,需要额外按端点累计权值。若只问回文总数,直接求半径和即可,不必构造差分数组。
 
+## 高阶扩展:ukuku——从半径数组反造字符串
+
+### 来源信息、题意与范围
+
+- VJudge:[AtCoder-qupc2018_h ukuku](https://vjudge.net/problem/AtCoder-qupc2018_h)
+- 原来源:九州大学程序设计竞赛 2018 H
+- 大致难度:高阶构造;主要考点:Manacher 半径的逆问题、镜像相等约束、区间并查集
+
+给出 $N\le2\times10^5$ 与数组 $A$。构造一个小写字符串 $S$,使以第 $i$ 个字符为中心的最长奇回文长度**恰为** $A_i$。输入保证至少存在一个答案。
+
+<details><summary>Hint 1:恰为 A_i 分成哪两类约束</summary>
+
+令 $r_i=(A_i-1)/2$。对 $1\le d\le r_i$ 必须有 $S_{i-d}=S_{i+d}$;若两侧仍在串内,则第 $r_i+1$ 层必须不等。
+
+</details>
+
+<details><summary>Hint 2:为什么逐对合并会超时</summary>
+
+串全相等时 $\sum r_i=\Theta(N^2)$。一次回文约束其实是“左区间正序 = 右区间逆序”,应批量合并两个有方向的区间对象。
+
+</details>
+
+<details><summary>Hint 3:怎样把区间相等下传到字符</summary>
+
+为每个 $2^k$ 长度区间建立正向、反向两个节点。合并两个同长度序列;从大层向小层,把一个等价类的前半与前半、后半与后半继续合并,最终落到单字符。
+
+</details>
+
+### Observations
+
+1. 正向 Manacher 由字符串求半径;逆问题中半径直接产生等式与不等式,目标对象应从“中心扩展过程”换成“字符等价类”。
+2. 只合并每个回文最外层字符不够,例如半径 3 同时要求三对字符相等;真正删掉二次工作量的是幂长区间复用。
+3. 所有等式合并完后,每个等价类取一个字符;“最长恰好”只剩等价类之间的不等边,按最早出现顺序贪心染最小可用字母即可。存在性保证不会用尽 26 色。
+
+### Editorial 与正确性证明
+
+每个中心 $i$ 给出长度 $r_i$ 的序列等式
+
+$$
+S[i-r_i\ldots i-1]
+=
+\operatorname{reverse}(S[i+1\ldots i+r_i]).
+$$
+
+把长度按二进制拆成 $O(\log N)$ 个块。第 $k$ 层节点表示某个长度 $2^k$ 的正向或反向序列;同层并查集合并表示两段逐位相等。全部约束加入后从高层向低层传播:正向块的孩子是左正向、右正向;反向块的孩子顺序是右反向、左反向。归纳可知,第 0 层两个位置同属一类,当且仅当它们被全部回文内部等式强制相等。
+
+对每个中心,若 $i-r_i-1$ 与 $i+r_i+1$ 均存在,就在它们的第 0 层等价类间加不等边。给类染色后,同半径内的镜像字符因并查集合并而相等,所以长度至少为 $A_i$;边界两字符因不等边异色,所以不能再扩一层,最长长度恰为 $A_i$。每个回文加入 $O(\log N)$ 次合并,分层节点总数 $O(N\log N)$,总时间与空间均为 $O(N\log N)$。
+
+### 核心伪代码、实现边界与附件
+
+```text
+for every power k:
+    make DSU nodes for every length-2^k interval in forward/reverse direction
+for center i:
+    decompose its left half and reversed right half into equal power blocks
+    union corresponding directed blocks
+for k from high to 1:
+    for each node equal to another node:
+        union their first children and union their second children at k-1
+for center i with two outside characters present:
+    add inequality(class(left_outside), class(right_outside))
+greedily color character classes with letters a..z
+```
+
+半径只含奇数;中心和区间下标必须统一为 0 基。第 0 层的正反节点表示同一字符,初始化时就应合并。完整 GNU C++17 附件:[AtCoder-qupc2018_h.cpp](../../参考实现/04-KMP与Manacher/AtCoder-qupc2018_h.cpp)
+
+### 复盘与变式
+
+识别信号是“大量区间逐位相等,且一侧可能反向”。若输入不保证有解,需要额外检查:等式类内部不能出现不等边,染色不能超过字母表,并在输出后重新跑一次 Manacher 作最终验证。偶回文也可用同一结构,只需把两半的边界定义改到字符缝隙。
+
 ## 扩展与联系
 
 - Manacher 擅长静态单串全部中心;动态修改或两串回文比较通常需要哈希、线段树或回文树等工具。

+ 96 - 0
课程/05-最短路与类最短路/03-最短路树次短路与差分约束.md

@@ -157,6 +157,102 @@ else: output dist[N], the tightest upper bound on x[N]-x[1]
 
 若要求一组具体坐标,可从可行势能恢复并整体平移;若额外规定 $x_1=0$ 且所有变量有上下界,把这些边一并加入超级源系统即可。
 
+## 高阶扩展:01 Balanced——最优字符串其实是最短路距离
+
+### 来源信息、题意与范围
+
+- VJudge:[AtCoder-agc056_c 01 Balanced](https://vjudge.net/problem/AtCoder-agc056_c)
+- 原题:[AtCoder Grand Contest 056 C](https://atcoder.jp/contests/agc056/tasks/agc056_c)
+- 官方难度口径:900 分;考点:前缀势能、差分约束、0-1 BFS、奇偶收紧
+
+构造长度 $N$ 的 01 串。每个条件 $[L_i,R_i]$ 要求其中 0、1 数量相等;输出满足全部条件的字典序最小串。$2\le N\le10^6$,$1\le M\le2\times10^5$,区间长度均为偶数,题目保证有解。
+
+<details><summary>Hint 1:把字符改写成前缀高度</summary>
+
+令 0 贡献 $+1$、1 贡献 $-1$,$v_i$ 为前 $i$ 位之和。区间平衡等价于 $v_{L_i-1}=v_{R_i}$。
+
+</details>
+
+<details><summary>Hint 2:先把等于 1 放宽成至多 1</summary>
+
+相邻前缀原本要求 $|v_i-v_{i-1}|=1$。暂改成 $\le1$,再问在 $v_0=0$ 下每个 $v_i$ 最大能有多大。
+
+</details>
+
+<details><summary>Hint 3:上界为什么是最短路</summary>
+
+相邻位置连权 1 双向边,等式端点连权 0 双向边。任一路径都给出 $v_x\le v_0+\operatorname{len(path)}$,最紧上界就是从 0 出发的最短距离;整组最短距离本身满足全部差分约束。
+
+</details>
+
+### Observations
+
+1. 逐位尝试 0 再做可行性检查需要 $N$ 次全局判定;前缀和把字典序目标变成按顺序最大化 $v_1,v_2,\ldots$。
+2. 等式不是额外 DP 状态,而是零代价边;相邻变化至多 1 是单位边。最大可行势能恰等于最短路给出的最紧上界。
+3. 放宽后看似可能出现相邻高度相等,但位置 $i$ 的前缀高度奇偶固定为 $i$。所有零边连接同奇偶位置,所以最短距离也保留该奇偶;相邻距离差至多 1 且奇偶不同,只能恰差 1。
+
+### Editorial、证明与复杂度
+
+定义
+
+$$
+v_i=\#0(s_1\ldots s_i)-\#1(s_1\ldots s_i),\qquad v_0=0.
+$$
+
+字典序更小意味着在第一个不同位置选择 0,即让对应前缀高度更大,因此要字典序最大化向量 $(v_1,ldots,v_N)$。建 $N+1$ 点图:$i-1\leftrightarrow i$ 权 1;每个条件 $L_i-1\leftrightarrow R_i$ 权 0。任意可行 $v$ 都满足每条边 $v_y\le v_x+w$,所以 $v_i\le\operatorname{dist}(0,i)$。另一方面,最短距离满足三角不等式,故把 $v_i$ 取为距离可同时达到每个点的最大上界。
+
+零边连接下标差为偶数的点,单位边改变一次下标奇偶,所以任意从 0 到 $i$ 的路长度奇偶与 $i$ 相同,最短距离亦同。于是相邻距离不能相等,只能相差 1;放宽解自动回到合法 01 串。若 $v_i=v_{i-1}+1$ 输出 0,否则输出 1。图边权只有 0/1,用 0-1 BFS,时间 $O(N+M)$、空间 $O(N+M)$,可承受 $N=10^6$。
+
+### 核心伪代码、边界与附件
+
+```text
+for i in 1..N: add bidirectional edge (i-1, i, weight=1)
+for condition (L, R): add bidirectional edge (L-1, R, weight=0)
+dist = zero_one_BFS(source=0)
+for i in 1..N:
+    answer[i] = '0' if dist[i] > dist[i-1] else '1'
+```
+
+不能把 0/1 字符值直接当边权;图上距离表示的是“高度最大上界”。内存上用紧凑邻接表更稳,附件的 `vector` 写法在题目 1024 MiB 限制内仍可通过。完整 GNU C++17 附件:[AtCoder-agc056_c.cpp](../../参考实现/05-最短路与类最短路/AtCoder-agc056_c.cpp)
+
+### 复盘与变式
+
+核心迁移是“最大化满足 Lipschitz 约束的势能 = 从锚点出发的最短距离”。若区间长度允许为奇数,零边可能连接异奇偶位置,放宽解未必满足相邻差恰为 1,必须重新处理可行性,不能照搬结论。
+
+## 灵活性实验室:Walk Around Neighborhood
+
+### 来源与为什么放在本章
+
+- VJudge:[AtCoder-agc062_d Walk Around Neighborhood](https://vjudge.net/problem/AtCoder-agc062_d)
+- 原题:[AtCoder Grand Contest 062 D](https://atcoder.jp/contests/agc062/tasks/agc062_d)
+- 官方难度口径:1100 分;$N\le2\times10^5$,所有偶数步长 $2\le D_i\le2\times10^5$
+
+每次任选一个未用步长 $d$,在平面上移动恰好曼哈顿距离 $d$;用完后回原点,并最小化途中到原点曼哈顿距离的最大值。它不要求跑 Dijkstra,因此不占“最短路算法”正式名额;保留在此是为了训练删状态:二维坐标最终只需其到原点的距离 $R$。
+
+<details><summary>关键观察 1:两个半径之间何时能走一步</summary>
+
+从半径 $R$ 的任一点走距离 $D$ 到半径 $R'$ 的某点,充要条件是 $|R-R'|\le D\le R+R'$。曼哈顿圆周的连续性保证区间内每个 $D$ 都能实现。
+
+</details>
+
+<details><summary>关键观察 2:固定顺序与上界 K</summary>
+
+前缀距离未触及 $K$ 前只能向外积累,后缀同理。第一次到达半径 $K$ 的步与最后一次离开半径 $K$ 的步给出两个局部三角不等式;中段所有 $D_i\le2K$ 时可以一直停留在半径 $K$ 的圆周上。
+
+</details>
+
+<details><summary>关键观察 3:允许重排后为什么出现部分和</summary>
+
+当最大步长 $M>K\ge M/2$ 时,需要在两个大步长前分别垫入一些不超过 $K$ 的小步长,使局部三角不等式成立。最优选择归结为某个部分和是否落入区间 $[A-K,\ S-(B-K)]$。
+
+</details>
+
+### 不做题也应带走的 Editorial 主线
+
+先用总和 $S<2M$ 排除无解,这是闭合折线的三角不等式。固定答案上界 $K$ 后,状态不是坐标集合,而是可达半径集合;上面的充要条件使它保持为连续区间。若 $K\ge M$ 总能安排;否则只需考虑 $M/2\le K<M$,挑出大于 $K$ 的两个最小值 $A,B$,小值部分和要同时为两端提供“缓冲”。当小值总和足够大,升序贪心跨过下界时不会越过上界;只有总和 $O(M)$ 的困难区间需要按值频数做有界部分和。结合答案二分可做到官方给出的 $O(M\sqrt M)$ 量级。
+
+这道题的失效边界很重要:若距离换成离散障碍图上的最短路,半径相同的点不再等价,可达集合也未必连续;此时不能把坐标删成一个 $R$。本题作为阅读实验室,不设为后续硬前置,也不声称附件已完成。
+
 ## 扩展与联系
 
 - 最短路 DAG 上可计最短路条数、判必经边;有零权边时先处理等距环。

+ 98 - 0
课程/05-最短路与类最短路/04-高阶扩展-周期内进度状态.md

@@ -0,0 +1,98 @@
+# 高阶扩展:把等待周期压成 0-1 状态图
+
+## 本节目标
+
+面对“绿灯内必须连续移动、红灯只能停在安全点”的时间规则,分离完整周期数与本周期已用时间,用 0-1 BFS 取代对绝对时间的搜索。
+
+## 前置知识与课前自测
+
+前置:0-1 BFS、状态图、排序。
+
+1. 若状态只记安全岛编号,会丢失什么?
+2. 到达安全岛时绿灯恰好结束,与尚有余量的代价差别是什么?
+3. 为什么只向排序后相邻安全岛转移仍不漏最优路线?
+
+<details><summary>答案</summary>
+
+1. 本轮绿灯还剩多少时间。
+2. 前者必须经历一次红灯并把轮内进度清零;后者无需新增完整周期。
+3. 同方向越过中间安全岛可拆成若干相邻段,总距离和轮内进度不变。
+
+</details>
+
+## 问题动机
+
+以绝对时间建图会把大量只差完整周期数的状态分开;只记位置又无法判断当前绿灯是否还走得动。需要保留“相位”,把完整周期折叠进距离。
+
+## 精选例题:Nastya and Unexpected Guest
+
+### 来源与题意
+
+- VJudge:[CodeForces-1340C](https://vjudge.net/problem/CodeForces-1340C)
+- 原题:[Codeforces 1340C](https://codeforces.com/problemset/problem/1340/C)
+- 难度:2400;考点:周期状态、0-1 BFS、终点特殊处理
+
+道路是 $[0,n]$,$n\le10^6$;有 $m\le10^4$ 个安全岛,含 0 与 $n$。绿灯 $g$ 秒、红灯 $r$ 秒,$g,r\le1000$。绿灯时每秒走一单位且不能停,只有安全岛可变向;红灯开始时必须在安全岛。求从 0 到 $n$ 的最短时间。
+
+<details><summary>Hint 1</summary>状态设为 `(island, spent)`,其中 `spent` 是当前绿灯已经走的秒数。</details>
+<details><summary>Hint 2</summary>让距离只记录已经完整经历的“绿+红”周期数。</details>
+<details><summary>Hint 3</summary>移动后 `spent&lt;g` 是 0 边;`spent==g` 必须等待红灯、重置为 0,是 1 边。</details>
+<details><summary>Hint 4</summary>到终点即结束;若恰在第 $g$ 秒到达终点,不能把最后一段红灯算进去。</details>
+
+## Observations 与 Editorial
+
+绝对时间可很大,但规则只关心时间在周期中的相位。定义状态 $(i,s)$:人在第 $i$ 个安全岛,当前绿灯已经走 $s$ 秒,$0\le s<g$。距离 `cycle[i][s]` 是此前已经结束并等待过红灯的完整周期数。
+
+从 $i$ 到相邻安全岛 $j$ 距离为 $d$:
+
+- $s+d>g$ 不合法;
+- $s+d<g$ 转到 $(j,s+d)$,边权 0;
+- $s+d=g$,若还没结束旅程,就必须等红灯并转到 $(j,0)$,边权 1。
+
+边权只有 0/1,用 deque。每弹出状态还单独检查能否在剩余 $g-s$ 秒内直接向右到达 $n$,候选真实时间为
+
+$$
+cycle(i,s)(g+r)+s+(n-d_i).
+$$
+
+这一步也正确处理“恰好到终点”:到达即停止,不加红灯。
+
+### 正确性证明与复杂度
+
+任一合法行程在每次经过安全岛时产生上述状态;同一绿灯内的连续移动对应若干 0 边,绿灯用满且未结束对应一条 1 边。因此行程映射为状态路径,完整周期数与轮内时间的公式等于真实用时。
+
+反之每条状态边都满足绿灯余量,1 边只在安全岛等待,故状态路径可还原为合法行程。非相邻跳跃可沿同方向经过的中间安全岛拆分,不引入等待或转向,所以只连相邻岛不漏解。0-1 BFS 最小化完整周期数;对同一状态轮内时间固定,故最终枚举得到最短真实时间。
+
+状态数 $O(mg)\le10^7$,每状态至多两条转移,时间与空间均为 $O(mg)$。
+
+## 实现指南、核心伪代码与附件
+
+```text
+sort safety islands
+dist[start][0] = 0
+0-1 BFS over (island i, spent s):
+    try finishing directly if n-position[i] <= g-s
+    for adjacent island j:
+        ns = s + abs(position[j]-position[i])
+        if ns < g: relax (j,ns) with cost 0
+        if ns == g: relax (j,0) with cost 1
+answer = minimum completed*(g+r) + spent + final_distance
+```
+
+[完整 GNU C++17 实现](../../参考实现/05-最短路与类最短路/CodeForces-1340C.cpp)
+
+- 状态数组不存 `spent=g`,该相位立即重置。
+- 直接把 1 边权写成 `g+r`、0 边写成路长并不能得到普通最短路,因为轮内进度仍约束后续合法性。
+- 若绿灯中允许原地等待,则到达安全岛后的可选相位增加,当前唯一进度转移失效。
+
+## 扩展与联系
+
+这是“商作为距离、余数作为状态”的典型模型。若每轮规则由多个阶段组成,可把余数状态改成有限自动机;只有阶段切换代价仍为 0/1 时才能继续使用 0-1 BFS。
+
+## 总结与自检
+
+请复述:为何距离只存周期数仍足够?为何终点不能作为普通 `spent==g` 转移处理?只连相邻安全岛的完备性依赖哪个条件?
+
+## 课后训练建议
+
+先写状态 Dijkstra 验证模型,再替换为 0-1 BFS;用小坐标全时间 BFS 对拍。卡在状态定义 25 分钟后看 Hint 1--2。

+ 88 - 0
课程/06-最小与最大生成树/03-Kruskal重构树与XORMST.md

@@ -121,6 +121,94 @@ solve(group, bit):
 
 [CodeForces-891C Envy](https://vjudge.net/problem/CodeForces-891C) 要判断每个询问给定的边集能否同时出现在某棵 MST。提示方向:按权值处理询问边,在全局 DSU 只合并更轻边;同一询问、同一权组用临时可回滚 DSU 检查是否成环。它把本章“同权批次不能提前合并”的原则提升到离线多询问,不作为后续硬前置。
 
+## 高阶扩展:Blue and Red Tree——把操作顺序倒过来同步缩点
+
+### 来源信息、题意与范围
+
+- VJudge:[AtCoder-agc014_e Blue and Red Tree](https://vjudge.net/problem/AtCoder-agc014_e)
+- 原题:[AtCoder Grand Contest 014 E](https://atcoder.jp/contests/agc014/tasks/agc014_e)
+- 官方难度口径:1400 分;考点:逆序操作、两棵树同步缩点、启发式合并
+
+初始蓝树与目标红树各有 $N$ 个点。一次操作在当前全蓝边路径上删一条蓝边,再连接路径两端加入红边;共做 $N-1$ 次。判断能否恰好得到目标红树。$2\le N\le10^5$。
+
+<details><summary>Hint 1:正向模拟为什么难</summary>
+
+加入红边后它不能再出现在“全蓝路径”上;一次选择会改变后续所有可用路径,局部贪心没有明显安全性。
+
+</details>
+
+<details><summary>Hint 2:倒放最后一次操作</summary>
+
+最后只剩一条蓝边要删、最后一条红边要加。把已经处理完的点集看成连通块,这两条边必须连接同一对块。
+
+</details>
+
+<details><summary>Hint 3:缩点后的贪心条件</summary>
+
+只要当前有一对连通块之间同时存在蓝边与红边,就可安全合并;重复到只剩一个块即为可行。
+
+</details>
+
+### Observations
+
+1. 正向状态需记录哪些红边已加入、哪些蓝边仍在,搜索空间指数级;倒序只关心尚未展开的连通块划分。
+2. 若可行,按蓝边被删除的逆序看,每一步对应的一蓝一红边连接同一对当前块;这把“路径操作”改写为“两棵树同步收缩”。
+3. 可合并对不必猜顺序:收缩一对合法块只是在任何可行收缩序列中提前处理这两个不可再区分的块,不会破坏其余边的颜色存在性。
+
+### Editorial、证明与复杂度
+
+从单点划分开始。若两个当前块 $A,B$ 之间蓝、红边都存在,就合并它们。必要性来自倒放原操作:设正向第 $i$ 次删蓝边 $b_i$、加红边 $r_i$,在更晚的蓝边已删完后,$b_i,r_i$ 的端点落在同一对剩余蓝森林连通块中。逆序便是一串合法同步合并。
+
+充分性也按合并序列倒放:若依次用蓝边 $b_i$、红边 $r_i$ 合并块,把顺序反转;当执行对应正向操作时,红边两端在当前蓝森林中由该次蓝边所在路径连接,删 $b_i$ 再加 $r_i$ 合法。故能合成一个块当且仅当原操作可行。
+
+实现为每个并查集根维护 `neighbor -> color_mask`,蓝为 1、红为 2;掩码为 3 的块对进入队列。合并时把较小邻接表逐项搬到较大表,并同步改写邻块中的反向键。每条表项至多被搬 $O(\log N)$ 次,期望时间 $O(N\log N)$、空间 $O(N)$。
+
+### 核心伪代码、边界与附件
+
+```text
+for every blue edge between components u,v: adjacency[u][v] |= BLUE
+for every red edge between components u,v: adjacency[u][v] |= RED
+queue every pair whose mask is BLUE|RED
+while queue not empty:
+    u,v = current DSU roots of the pair
+    if u == v or current mask is not both colors: continue
+    merge the smaller adjacency map into the larger one
+    whenever a merged neighbor mask becomes both colors, enqueue that pair
+answer YES iff exactly one component remains
+```
+
+同色树不可能在两个已由同色边连通的块间留下两条独立跨边,否则原树成环,因此两位掩码足以,不需记录重数。队列项会过期,弹出后必须重新 `find` 并复查掩码。完整 GNU C++17 附件:[AtCoder-agc014_e.cpp](../../参考实现/06-最小与最大生成树/AtCoder-agc014_e.cpp)
+
+### 复盘与变式
+
+识别信号是“两个树结构按同一连通块划分逐步收缩”。若蓝图或红图不是树,同色平行跨边可能有实际重数含义,掩码实现和充分性证明都需重做。
+
+## 衔接题:Red and Blue Spanning Tree
+
+### 来源与定位
+
+- VJudge:[AtCoder-agc064_b Red and Blue Spanning Tree](https://vjudge.net/problem/AtCoder-agc064_b)
+- 原题:[AtCoder Grand Contest 064 B](https://atcoder.jp/contests/agc064/tasks/agc064_b)
+- 官方难度口径:600 分;$N,M\le2\times10^5$
+
+边有红蓝色,每个点也指定一种颜色;构造一棵生成树,使每个点至少关联一条与自身指定颜色相同的树边。它作为“先满足约束森林、再补成生成树”的台阶,不占三个正式高阶名额。
+
+<details><summary>Hint 1</summary>把边分为同时满足两端、只满足一端、两端都不满足三类。</details>
+<details><summary>Hint 2</summary>第一类边尽量用并查集取成森林;被选边的两个端点都已满足。</details>
+<details><summary>Hint 3</summary>第二类边从“已满足但不受益的一端”向“尚未满足且受益的一端”传播,最后任意补边连通森林。</details>
+
+第一阶段选第一类边的极大无环子集。若一个第一类连通块含边,其中所有非孤立点都已满足。对第二类边定向:边色不匹配的端点指向匹配的端点;从已满足点 BFS,每次选边把新点纳入森林并标记满足。未能到达的点若存在,则它所在第二类结构只能靠每点各取一条边而必成环,故无解;全部满足后,原图连通保证可用剩余任意颜色边做 Kruskal 补成树。三阶段都只加跨并查集边,所以结果无环;最终有 $N-1$ 边且连通。时间 $O((N+M)\alpha(N))$。
+
+```text
+take a maximal forest of edges whose color matches both endpoints
+direct every one-end-matching edge from non-beneficiary to beneficiary
+BFS from already satisfied vertices and select propagation edges
+if some vertex is unsatisfied: No
+otherwise Kruskal-add arbitrary remaining edges until connected
+```
+
+完整 GNU C++17 附件:[AtCoder-agc064_b.cpp](../../参考实现/06-最小与最大生成树/AtCoder-agc064_b.cpp)
+
 ## 扩展与联系
 
 - Kruskal 重构树也可按边权建,用于路径瓶颈、阈值连通与离线询问。

+ 91 - 0
课程/06-最小与最大生成树/04-高阶扩展-关联图中的最大森林.md

@@ -0,0 +1,91 @@
+# 高阶扩展:从彩虹环到关联图最大生成森林
+
+## 本节目标
+
+把“每个集合内部产生同色团”的彩虹环条件,转写成集合—元素二分关联图中的普通环;再把最小删除代价取补,变成最大权森林。
+
+## 前置知识与课前自测
+
+前置:DSU、Kruskal、二分关联图。
+
+1. 一个集合节点连接多个元素节点时,两条关联边能代表原图中的什么?
+2. 二分关联图的一条交替环怎样投影成原图彩虹环?
+3. 最小删除权与最大保留权如何互补?
+
+<details><summary>答案</summary>
+
+1. 两元素间一条颜色为该集合编号的边。
+2. 每经过一个集合节点,就在它相邻的两个元素间取一条该颜色边;环上集合节点互异时颜色互异。
+3. 删除权等于全部权减保留权。
+
+</details>
+
+## 问题动机
+
+每个集合在原图中展开成同色团,显式边数可能平方级;而“彩虹”限制又要求记住边来自哪个集合。把集合升格为节点,可以同时压缩团并保留颜色身份。
+
+## 精选例题:Avoid Rainbow Cycles
+
+### 来源与题意
+
+- VJudge:[CodeForces-1408E](https://vjudge.net/problem/CodeForces-1408E)
+- 原题:[Codeforces 1408E](https://codeforces.com/problemset/problem/1408/E)
+- 难度:2400;考点:关联图、环的等价转化、最大生成森林
+
+有 $m,n\le10^5$,集合 $A_i\subseteq[1,n]$,总出现次数不超过 $2\times10^5$。保留元素 $j\in A_i$ 后,集合内任意两保留元素间都有一条颜色 $i$ 的边;删除该出现需付 $a_i+b_j$。求使图中没有边颜色互异的彩虹环的最小代价。
+
+<details><summary>Hint 1</summary>建立左侧集合 $i$、右侧元素 $j$ 的二分图,每次出现是一条权为 $a_i+b_j$ 的边。</details>
+<details><summary>Hint 2</summary>保留的关联边若成环,环中集合点与元素点交替;把每个集合点两侧的元素直接相连。</details>
+<details><summary>Hint 3</summary>反过来,彩虹环的每条颜色选择其集合点,可得到不重复集合点的关联闭合游走,并抽出一个环。</details>
+<details><summary>Hint 4</summary>因此保留边必须构成森林;最大化保留权即可。</details>
+
+## Observations、Editorial 与证明
+
+原图把每个集合展开成同色团,边数可能平方级;关联图只为每次出现建一条边,总规模线性。
+
+若关联图有简单环
+
+$$
+j_1-i_1-j_2-i_2-\cdots-j_k-i_k-j_1,
+$$
+
+则原图中 $j_t,j_{t+1}$ 同属 $A_{i_t}$,可取颜色 $i_t$ 的边;简单环上的集合点互异,得到彩虹环。
+
+若原图有彩虹环,对每条颜色边 $(x,y)$ 插入对应集合点 $i$,得到关联图闭合游走。颜色互异保证这些集合点不重复;闭合游走中可删去重复片段抽出简单环。因此“无彩虹环”等价于“保留出现边在关联图中无环”。
+
+每条出现边删除代价就是其权。设全部权和为 $S$,删除集合代价为 $S-$保留权。合法保留集是森林,所以目标是最大权森林;所有权为正,按权降序运行 Kruskal,能加入且不成环的边全部加入。图可不连通,故是森林而非强求生成树。
+
+环等价证明给出可行域一致;Kruskal 的割性质给出最大保留权,取补即最小删除代价。时间 $O(K\log K)$、空间 $O(m+n+K)$,$K=\sum|A_i|\le2\times10^5$。
+
+## 实现指南、核心伪代码与附件
+
+```text
+total = 0
+for every occurrence j in set i:
+    add bipartite edge (set_i, value_j) with weight a[i]+b[j]
+    total += weight
+sort all incidence edges by decreasing weight
+kept = sum of edges accepted by DSU Kruskal
+answer = total - kept
+```
+
+[完整 GNU C++17 实现](../../参考实现/06-最小与最大生成树/CodeForces-1408E.cpp)
+
+## 扩展与联系
+
+关联图把“超边/集合”降成普通二分边。类似技巧还用于超图连通性与矩阵行列关系;但具体禁形是否等价于关联图环,必须逐方向证明。
+
+## 实现边界与复盘
+
+- 同一元素在同一集合内互异,但不同集合可形成平行的原图边;关联图用不同集合节点自然区分颜色。
+- 不能直接在 $n$ 个元素点上跑 Kruskal:一条“保留出现”不是原图的一条固定元素边。
+- 若禁止的是任意环而非彩虹环,关联图环等价不再成立,同一集合团内部的三角形就已违规。
+- 识别信号是“对象属于若干组,组内任意两点建立同标签关系”;先尝试把组本身升格为节点。
+
+## 总结与自检
+
+请分别证明关联图环推出彩虹环、彩虹环推出关联图环。为什么目标是最大生成森林而不是最小生成树?
+
+## 课后训练建议
+
+先证明两个方向的环对应,再写代码;若只记住“二分图 + 最大生成树”,换题后很容易误用。用小关联图枚举保留边集对拍。

+ 87 - 0
课程/07-二分图/01-二分染色与构造选择.md

@@ -142,6 +142,93 @@ output the two colors at each romantic pair's chair positions
 
 ## 扩展与联系
 
+## 高阶例题:Choosing Points
+
+### 来源信息与题意
+
+- VJudge:[AtCoder-agc025_d Choosing Points](https://vjudge.net/problem/AtCoder-agc025_d)
+- 原题:[AtCoder Grand Contest 025 D](https://atcoder.jp/contests/agc025/tasks/agc025_d)
+- 难度:AGC 800 分;考点:距离图二染、数论奇偶性、两次染色取交
+
+给定 $N,D_1,D_2$,其中 $1\le N\le300$,$1\le D_1,D_2\le2\times10^5$。从所有满足 $0\le x,y<2N$ 的格点中选出 $N^2$ 个,使任意两点的距离都不等于 $\sqrt{D_1}$ 或 $\sqrt{D_2}$。
+
+这题的难点不是搜索点集,而是证明每一种平方距离都能把整张格点图切成两侧。
+
+<details><summary>Hint 1:先只禁止一个距离</summary>
+
+把每个格点看成点,距离平方为 $D$ 的两点连边。若能把这张图二染,同色点集就不会包含禁边。
+
+</details>
+
+<details><summary>Hint 2:距离平方的模 4 信息</summary>
+
+若 $D$ 为奇数,则 $\Delta x,\Delta y$ 一奇一偶;若 $D\equiv2\pmod4$,则二者都为奇数。分别考虑 $(x+y)\bmod2$ 与 $x\bmod2$。
+
+</details>
+
+<details><summary>Hint 3:$4\mid D$ 怎么办</summary>
+
+$\Delta x^2+\Delta y^2\equiv0\pmod4$ 强迫 $\Delta x,\Delta y$ 都是偶数。把坐标同时除以 2,问题缩成距离平方 $D/4$;点的颜色可以递归地由 $(\lfloor x/2\rfloor,\lfloor y/2\rfloor)$ 决定。
+
+</details>
+
+<details><summary>Hint 4:同时禁止两个距离</summary>
+
+分别得到两位颜色 $(c_1,c_2)$,把 $4N^2$ 个格点分进四个桶。鸽巢原理保证某桶至少有 $N^2$ 个点。
+
+</details>
+
+### Observations
+
+1. 直接贪心选点需要动态检查大量平方距离;更重要的是,它解释不了题目保证的“任意 $D_1,D_2$ 都有解”。改看禁距图后,目标变成找一个大独立集。
+2. 一般图的最大独立集仍然困难;本题真正删掉搜索的是“每个禁距图都可显式二染”。只禁止 $D$ 时,同一颜色类天然是独立集。
+3. $D$ 的全部素因子并不重要。不断删除因子 4 后,只剩“奇数”或“模 4 余 2”两种奇偶骨架;它们分别由棋盘色和横坐标奇偶处理。
+4. 对两个禁距图不必求公共独立集。两次二染交叉成四类,同一类在两张图中都独立;四类平均大小恰为 $N^2$。
+
+### Editorial、正确性与复杂度
+
+定义 `color(x,y,D)`:只要 $4\mid D$,就令 $D\leftarrow D/4$ 且 $x\leftarrow\lfloor x/2\rfloor,y\leftarrow\lfloor y/2\rfloor$。终止后:
+
+- 若 $D$ 为奇数,返回 $(x+y)\bmod2$;
+- 若 $D\equiv2\pmod4$,返回 $x\bmod2$。
+
+证明它是距离图的合法二染。设两格点差为 $(u,v)$ 且 $u^2+v^2=D$。当 $4\mid D$ 时,平方模 4 只能是 0 或 1,因此 $u,v$ 必须同为偶数;约去 2 后仍是同一形式。约分终止后,若 $D$ 奇,则 $u,v$ 一奇一偶,$(x+y)$ 奇偶必翻转;若 $D\equiv2\pmod4$,则 $u,v$ 都奇,$x$ 奇偶必翻转。因此每条距离平方为 $D$ 的边两端异色。
+
+对 $D_1,D_2$ 分别算颜色,把点放入编号 `2*c1+c2` 的桶。同桶两点在两种染色下都同色,故不可能相距 $\sqrt{D_1}$ 或 $\sqrt{D_2}$。总点数为 $(2N)^2=4N^2$,至少一个桶有 $N^2$ 个点,取其中任意 $N^2$ 个即可。这同时证明构造不漏解目标,也说明无需尝试不同起点或回溯。
+
+枚举格点的时间为 $O(N^2\log\max(D_1,D_2))$,空间为 $O(N^2)$;递归层数只等于因子 4 的个数。
+
+### 核心伪代码与参考实现附件
+
+```text
+function color(x, y, D):
+    while D % 4 == 0:
+        D = D / 4
+        x = floor(x / 2)
+        y = floor(y / 2)
+    if D is odd:
+        return (x + y) % 2
+    return x % 2
+
+make four empty buckets
+for x in 0 .. 2*N-1:
+    for y in 0 .. 2*N-1:
+        key = 2*color(x,y,D1) + color(x,y,D2)
+        append (x,y) to bucket[key]
+output any N*N points from a bucket whose size is at least N*N
+```
+
+完整 GNU C++17 参考实现:[AtCoder-agc025_d.cpp](../../参考实现/07-二分图/AtCoder-agc025_d.cpp)
+
+### 实现细节、复盘与变式
+
+- 这里除的是点坐标本身的整数商;对一条实际禁距边,两端在每层的坐标差都是偶数,所以该染色证明不受低位舍弃影响。
+- 不要枚举满足 $u^2+v^2=D$ 的位移,颜色公式已经同时处理全部位移。
+- 识别信号是“两个关系各自可二染,但需要同时独立”;可迁移方法是取颜色向量,再用鸽巢原理选最大交类。
+- 若禁距数增加到 $k$ 个,同样会得到 $2^k$ 个桶,只能保证最大桶至少有总点数的 $2^{-k}$;本题的常数 `2N` 正好与两个约束匹配。
+
+## 扩展与联系
+
 - 二分图判定也可返回一条奇环作为不可行证据:遇到同色边后沿 BFS 父链找公共祖先。
 - 分量翻转 + 背包是常见结构,出现“每块二选一且总量固定”应主动识别。
 - 2-SAT 能表达更一般的二选一冲突;本节图染色是其更特殊且更快的情形。

+ 83 - 0
课程/07-二分图/04-高阶扩展-关联图欧拉平衡.md

@@ -0,0 +1,83 @@
+# 高阶扩展:二分关联图上的欧拉平衡
+
+## 本节目标
+
+把“每个序列左右各一半、每个数值左右出现次数也相等”转成二分图每个点入度等于出度,并用欧拉回路一次满足两族约束。
+
+## 前置知识与课前自测
+
+前置:二分关联图、无向欧拉回路。
+
+1. 一次元素出现同时属于哪两个对象?
+2. 若把一次出现视作边,序列内左右平衡对应哪个点的度数条件?
+3. 欧拉回路沿行走方向定向后,每点入度和出度有何关系?
+
+<details><summary>答案</summary>
+
+1. 它所在的序列,以及它的数值。
+2. 序列节点。
+3. 相等;每次进入必有一次离开。
+
+</details>
+
+## 问题动机
+
+逐数组均分和逐数值均分分别很容易,但一次出现同时影响两边配额,独立贪心会互相破坏。关联图把一次决策变成连接两种配额节点的一条边。
+
+## 精选例题:Fair Share
+
+### 来源与题意
+
+- VJudge:[CodeForces-1634E](https://vjudge.net/problem/CodeForces-1634E)
+- 原题:[Codeforces 1634E](https://codeforces.com/problemset/problem/1634/E)
+- 难度:2400;考点:双重配额、关联多重图、欧拉定向
+
+给 $m\le10^5$ 个偶数长度数组,总长度不超过 $2\times10^5$。把每次出现标为 L 或 R,使每个数组恰好一半为 L,并且 L、R 作为多重集完全相等;构造或判无解。
+
+<details><summary>Hint 1</summary>左侧建数组节点,右侧建离散化后的数值节点;每次出现是一条带原位置编号的边。</details>
+<details><summary>Hint 2</summary>数组节点度数已为偶数。数值节点度数为该值总出现次数;奇数时为什么必无解?</details>
+<details><summary>Hint 3</summary>所有点度数偶数时,每个连通分量都有欧拉回路。</details>
+<details><summary>Hint 4</summary>沿欧拉回路给边定向;从数组指向数值记 L,反向记 R。</details>
+
+## Observations、Editorial 与证明
+
+L、R 多重集相等,等价于每个数值的出现中 L 与 R 数量相等;数组约束则要求每个数组节点也左右相等。这不是两个独立背包,而是每次出现同时消耗一条“数组配额”和一条“数值配额”。关联图把这两个归属保留在同一条边上。
+
+必要性很直接:某数值总出现奇数次时无法均分。反之所有数值度数为偶数,加上题目保证的偶数数组长度,二分多重图每个点度数均偶;每个非空连通分量存在欧拉回路。
+
+按回路行走方向给每条出现边定向。闭合欧拉游走在每个点每进入一次就离开一次,故入度等于出度。在数组节点,把出边记 L、入边记 R,数组内部各半;在数值节点方向语义相反,但仍一半边从数组指来、一半指回数组,所以该值在 L、R 中出现次数相等。每条边恰对应一个原位置,输出不漏不重。
+
+离散化 $O(K\log K)$,Hierholzer $O(K)$,空间 $O(K)$,其中 $K\le2\times10^5$。
+
+## 实现指南、核心伪代码与附件
+
+```text
+for each occurrence at array i, position j, value x:
+    add an indexed edge between array_node(i) and value_node(x)
+if some value node has odd degree: output NO
+for every nonempty component:
+    run Hierholzer and orient each used edge along the tour
+    array -> value means L; value -> array means R
+write each edge label back to its original (i,j)
+```
+
+[完整 GNU C++17 实现](../../参考实现/07-二分图/CodeForces-1634E.cpp)
+
+## 扩展与联系
+
+“把边分成两类,使每点两类度数相等”与欧拉定向等价;本题的二分性只负责赋予方向 L/R 的统一语义,欧拉平衡本身适用于一般偶度多重图。
+
+## 实现边界与复盘
+
+- 相同数值可在同一数组重复,关联图因此是多重图;必须按边编号访问。
+- 只让每个数组交替写 `LRLR...` 不保证同一数值全局平衡;只按每个值配对又不保证数组配额。
+- 若某些数组长度为奇数,连序列节点都出现奇度,欧拉充要条件仍能判无解。
+- 若 L、R 需要按权重相等而非出现次数相等,入出度平衡不再表达目标。
+
+## 总结与自检
+
+为什么数值总出现次数为偶数既必要又充分?为何沿欧拉回路定向能同时平衡数组节点和数值节点?
+
+## 课后训练建议
+
+先在纸上画两个数组、三个数值的关联图并沿欧拉回路标方向;卡 20 分钟后看 Hint 3。复盘时写清“边的两端分别代表哪两种配额”。

+ 111 - 0
课程/08-高级线段树与树状数组/01-Fenwick离线计数与序关系.md

@@ -141,6 +141,117 @@ for r in 1..n:
 
 ## 扩展与联系
 
+## 高阶例题:Inversions
+
+### 来源信息与题意
+
+- VJudge:[AtCoder-agc023_e Inversions](https://vjudge.net/problem/AtCoder-agc023_e)
+- 原题:[AtCoder Grand Contest 023 E](https://atcoder.jp/contests/agc023/tasks/agc023_e)
+- 难度:AGC 1700 分;考点:受限排列计数、交换配对、零因子分段、Fenwick
+
+给定 $1\le N\le2\times10^5$ 与 $1\le A_i\le N$。对每个满足 $P_i\le A_i$ 的 $1..N$ 排列 $P$,计算其逆序数;求所有合法排列逆序数之和,模 $10^9+7$。
+
+<details><summary>Hint 1:先数合法排列</summary>
+
+按数值从 $N$ 到 1 放置。放值 $x$ 时,可用位置数为
+
+$$
+c_x=\#\{i:A_i\ge x\}-(N-x).
+$$
+
+</details>
+
+<details><summary>Hint 2:固定位置对,不要固定实际取值</summary>
+
+对 $i<j$,若 $A_i\le A_j$,把一个合法排列中 $P_i,P_j$ 交换。哪些排列能两两配成“一顺一逆”?
+
+</details>
+
+<details><summary>Hint 3:把较大上界降到较小上界</summary>
+
+若 $A_i\le A_j$,额外要求 $P_j\le A_i$,就等价于把第 $j$ 个上界从 $A_j$ 降到 $A_i$。此时交换两个位置仍合法,逆序恰占一半。
+
+</details>
+
+<details><summary>Hint 4:上界下降后,排列数怎样变化</summary>
+
+把一个上界从 $v$ 降到 $u\le v$,每个 $x\in(u,v]$ 的可选位置数从 $c_x$ 变成 $c_x-1$,所以计数比为
+
+$$
+R(u,v)=\prod_{x=u+1}^{v}\frac{c_x-1}{c_x}.
+$$
+
+若区间含 $c_x=1$,该比值就是 0,不能直接用逆元。
+
+</details>
+
+<details><summary>Hint 5:Fenwick 维护什么</summary>
+
+令 $Q_t=\prod_{x=1}^{t}(c_x-1)/c_x$,但按 $c_x=1$ 切段。扫描位置 $j$ 时,按此前的上界 $A_i$ 查询 $Q_{A_j}/Q_{A_i}$ 或其反向形式。
+
+</details>
+
+### Observations
+
+1. 枚举排列完全不可行;按值从大到小放置后,合法排列总数分解成独立乘积 $T=\prod_x c_x$。若某个 $c_x\le0$,不存在合法排列,答案立即为 0。
+2. 线性期望虽能把答案写成 $T\sum_{i<j}\Pr(P_i>P_j)$,但直接求两个位置的联合分布仍有 $O(N^2)$ 个状态。交换位置把概率问题改成“再收紧一个上界后的排列数”。
+3. 若 $u=A_i\le v=A_j$,收紧 $j$ 的上界到 $u$ 后,每个排列与交换 $i,j$ 后的排列配对,二者恰有一个逆序。因此该位置对贡献 $T\,R(u,v)/2$。
+4. 若 $u>v$,同理配对“不构成逆序”的排列,故贡献为 $T-T\,R(v,u)/2$。这解释了为什么先统计 $A$ 自身的逆序数,再加一正一负两类修正。
+5. 比值乘积含零时贡献应为 0。按前缀中 `c_x==1` 的出现次数分组,只有两上界位于同一组时才能用前缀乘积相除;这一步避免了对 0 求模逆。
+
+### Editorial 与正确性证明
+
+先计算全部 $c_x$ 与 $T=\prod c_x$。对任意 $u\le v$,把某个上界 $v$ 改为 $u$:对值 $x\in(u,v]$,该位置不再可选,所以降序放置时选择数由 $c_x$ 变为 $c_x-1$;其他值不变。因此新排列数确为 $T R(u,v)$。
+
+固定 $i<j$。若 $A_i\le A_j$,在上述收紧后的排列集合里,$P_i,P_j\le A_i$,交换保持两个上界约束;排列中两值不同,交换是无不动点对合,每对恰有一个 $P_i>P_j$,贡献为 $TR/2$。若 $A_i>A_j$,完全相同的对合证明“不逆序”数为 $TR/2$,故逆序数为 $T-TR/2$。这覆盖每个位置对且每个排列逆序恰按其位置对计一次。
+
+为在 $O(N\log N)$ 汇总,令
+
+$$
+Q_t=\prod_{1\le x\le t,\ c_x\ne1}\frac{c_x-1}{c_x}.
+$$
+
+在不跨越 `c_x==1` 的同一组内,$R(u,v)=Q_v/Q_u$。从左到右扫描当前上界 $v=A_j$,三个 Fenwick 分别维护此前上界的数量、$Q_u$ 之和与 $Q_u^{-1}$ 之和:
+
+- 数量树给出 $u>v$ 的对数,贡献基础项 $T$;
+- $u\le v$ 时累加 $Q_v/Q_u$;
+- $u>v$ 时减去 $Q_u/Q_v$;
+- 查询范围限制在 $v$ 所在的同一零因子组,跨组比值本来就是 0。
+
+最后把正负修正乘 $T/2$。预处理与扫描均为 $O(N\log N)$,空间 $O(N)$,能通过 $N=2\times10^5$。
+
+### 核心伪代码与参考实现附件
+
+```text
+for x from N down to 1:
+    c[x] = count(A[i] >= x) - (N-x)
+    if c[x] <= 0: return 0
+T = product(c[x])
+
+build Q[x] from factors (c[x]-1)/c[x]
+split value axis after every x with c[x] == 1
+
+for v in A from left to right:
+    base_inversions += count_of_inserted_values_greater_than(v)
+    correction += Q[v] * sum(1/Q[u]) for inserted u <= v in same group
+    correction -= (1/Q[v]) * sum(Q[u]) for inserted u > v in same group
+    insert v into the three Fenwick trees
+
+answer = T * base_inversions + T/2 * correction
+```
+
+完整 GNU C++17 参考实现:[AtCoder-agc023_e.cpp](../../参考实现/08-高级线段树与树状数组/AtCoder-agc023_e.cpp)
+
+### 实现细节、复盘与变式
+
+- `c_x` 是整数选择数,不是模数意义下的值;必须先用整数判断是否为 0。
+- 因为 $1\le c_x\le N<10^9+7$,除 `c_x-1=0` 外都可安全求逆;零因子用分组处理。
+- 相等上界 $u=v$ 时空乘积为 1,该位置对贡献 $T/2$,代码的闭区间查询必须包含相等值。
+- 识别信号不是“求期望就结束”,而是“交换两个位置后约束何时仍合法”;对合把联合分布删成一次计数比值。
+- 若约束改成位置允许任意集合而非前缀 $[1,A_i]$,收紧一个位置不再只让连续一段 $c_x$ 各减 1,乘积比与一维 Fenwick 都会失效。
+
+## 扩展与联系
+
 二维偏序常由排序固定一维、Fenwick 维护另一维;若还多一维,可能进入 CDQ 分治。Fenwick 的限制是合并信息必须可做前缀差或单调选择,任意区间复杂结构仍需线段树。
 
 ## 总结与自检

+ 54 - 0
课程/09-基环树/01-剥叶找环与环上统计.md

@@ -133,6 +133,60 @@ for each remaining directed cycle of length L:
 
 ## 扩展与联系
 
+## 灵活性实验室:Preserve Distinct(研究导读)
+
+### 定位与来源
+
+- VJudge:[AtCoder-agc062_f Preserve Distinct](https://vjudge.net/problem/AtCoder-agc062_f)
+- 原题:[AtCoder Grand Contest 062 F](https://atcoder.jp/contests/agc062/tasks/agc062_f)
+- 官方题解:[AtCoder Editorial](https://atcoder.jp/contests/agc062/editorial/6475)
+- 难度:AGC 2000 分;考点:随状态重建函数图、剥叶、环上相等弦的交错结构
+
+有 $N$ 个牌堆、数字 $1..M$,每个数字恰在两个不同牌堆各出现一次,且初始各堆顶数字互异。每次只能删除一个堆顶,并要求删除后所有非空堆顶仍互异;$M\le2\times10^5$,问最多删除多少张牌。
+
+本题保留为“灵活性实验室”,不作为本章后续硬前置。目前只给出经官方题解核对的建模主线,不提供未完成的参考实现,也不把它计作已通过质量门槛的正式例题。
+
+<details><summary>观察 1:状态怎样变成函数图</summary>
+
+当前第 $i$ 堆顶为 $a_i$,令 $p_i$ 为数字 $a_i$ 的另一张卡所在牌堆,从 $i$ 向 $p_i$ 连边。每个非空堆恰有一条出边,因此得到动态函数图。
+
+</details>
+
+<details><summary>观察 2:为什么先找入度 0 的堆</summary>
+
+若堆 $i$ 是当前函数图的叶子(入度 0),就没有别的堆顶数字把“另一张卡”放在堆 $i$ 内。它不会成为别人的立即冲突目标,可以从叶侧持续删除并在堆顶变化后更新函数图。反复处理会清掉所有非环部分。
+
+</details>
+
+<details><summary>观察 3:环为什么还不是普通环 DP</summary>
+
+删除某张牌会改变该堆出边,函数图本身随之变化。应截取每个环堆从当前堆顶到“前驱堆顶的另一张卡”之间的片段,再按环序拼成序列 $X$;每个数字在 $X$ 中出现零、一或两次。
+
+</details>
+
+<details><summary>观察 4:什么结构能打破环</summary>
+
+若 $X$ 中有只出现一次的数字,或有两对位置按 $x,\ldots,y,\ldots,x,\ldots,y$ 交错,则可以选择删除顺序打破当前环,之后重新出现叶子并继续剥除。真正阻塞的是所有相等位置对都呈不交错的嵌套结构。
+
+</details>
+
+### 不做题也应获得的考虑链
+
+1. **朴素状态失败。** 用每堆已删张数作状态有指数级组合;只看当前哪些数字冲突又丢失了牌堆内部顺序。
+2. **第一次换对象。** 不追踪全部牌面,而让每个当前堆顶指向其副本所在堆。合法删除的传播关系因此变成函数图的叶与环。
+3. **剥叶的含义变化。** 普通函数图剥叶一次就固定环;本题每次露出新堆顶后必须局部重建出边。算法维护的是“动态障碍图”,不是静态图模板。
+4. **第二次换对象。** 对剩余环,把各堆关键片段拼成循环序列 $X$。问题由牌堆操作转成圆周上相等位置配对的拓扑:单次出现或交错弦能制造破口,完全嵌套才形成最终障碍。
+5. **最终算法轮廓。** 栈可在线判断成对位置是嵌套还是交错,并在线枚举“沿相邻位置、再跳到相等位置”的交替环;给每个候选环累计最终必须保留的牌数,取最小值,再用总牌数减去它。官方题解给出整体 $O(M)$ 的实现方向。
+
+### 阅读验证与失效边界
+
+- 最小反例是三个堆顶副本首尾相接成环,且下一张恰是前驱堆顶:任何一步都会制造两个相同堆顶,答案可以是 0。
+- “叶子就整堆一次删完”是概念上的可交换性结论;实际实现仍要在每次新堆顶出现时更新其指向及相关入度。
+- 每个数字恰出现两次,使相等关系成为配对;若出现三次,`另一张卡所在堆` 不再唯一,函数图与弦匹配模型同时失效。
+- 当前缺口是把交替环的栈枚举、候选环权值与原牌堆下标写成一套可对拍的不变量。补齐前,本节只标研究导读,不给伪代码或附件,避免把轮廓误写成可提交算法。
+
+## 扩展与联系
+
 基环树最大独立集、直径等问题通常先做挂树 DP,再在环上做断环 DP。若每个分量可能有多环,就不再是基环树,剥叶后剩余的是一般 2-core,环上链式 DP 会失效。
 
 ## 总结与自检

+ 127 - 0
课程/09-基环树/02-高阶扩展-删环边直径与代数构造.md

@@ -0,0 +1,127 @@
+# 高阶扩展:基环上的最优化与代数构造
+
+## 本节目标
+
+完成两种不套路的函数图/基环树转化:把删一条环边后的直径写成环序列区间摘要;把受位置约束的整数直接解释为一条函数图边,并由有向环望文生义地消去总和。
+
+## 前置知识与课前自测
+
+前置:剥叶找环、树直径 DP、函数图、线段树区间合并。
+
+1. 连通 $n$ 点 $n$ 边图中,哪些边删除后仍连通?
+2. 两个不同环根的挂树点之间,删去一条环边后的最远距离由哪三部分组成?
+3. 若定义 $f(i)=i-a_i$,什么结构会让 $\sum(i-f(i))$ 自动望文生义为 0?
+
+<details><summary>答案</summary>
+
+1. 唯一环上的边。
+2. 两侧挂树高度、环被切成路径后的根间距离。
+3. $f$ 的一个有向环。
+
+</details>
+
+## 问题动机
+
+同样是“找到环”,后续可能完全不同:最优化题需要比较每个断环位置,构造题则可能让环上的望文生义直接消去目标量。本节专门训练找环后的第二次建模。
+
+## 精选例题 1:Roads in the Kingdom
+
+### 来源与题意
+
+- VJudge:[CodeForces-835F](https://vjudge.net/problem/CodeForces-835F)
+- 原题:[Codeforces 835F](https://codeforces.com/problemset/problem/835/F)
+- 难度:2500;考点:加权基环树、树直径、断环成链、可合并摘要
+
+连通无向图有 $n\le2\times10^5$ 个点和恰好 $n$ 条正权边。删一条边后仍须连通,最小化剩余图的直径。
+
+<details><summary>Hint 1</summary>只能删唯一环上的边;先对每个环点求挂树高度 $h_i$ 与挂树内部最大直径。</details>
+<details><summary>Hint 2</summary>删边后把环按某个起点展开为链,设环上坐标为 $d_i$。跨挂树直径是 $h_i+h_j+d_j-d_i$($i&lt;j$)。</details>
+<details><summary>Hint 3</summary>一个有序区间只需保存 `left=max(h-d)`、`right=max(h+d)` 与区间内 `best`。</details>
+<details><summary>Hint 4</summary>拼接 A+B 时,新跨区间答案是 `A.left+B.right`;把环复制两遍,查询每个长度为环点数的窗口。</details>
+
+### Observations 与 Editorial
+
+剥叶确定环。对每个环根,在不走环边的挂树中做后序 DP:最高向下距离是 $h_i$,两条最大子链之和更新全局 `tree_diameter`。这个值不受删哪条环边影响。
+
+若删去环边 $(s-1,s)$,剩余环按 $s,s+1,\ldots,s+k-1$ 成为一条加权链。给复制后的环点递增坐标 $d_i$,不同根之间可能形成的最长路径为
+
+$$
+\max_{i<j}\bigl((h_i-d_i)+(h_j+d_j)\bigr).
+$$
+
+对有序段维护三元摘要 $(L,R,B)$:$L=\max(h-d)$,$R=\max(h+d)$,$B$ 是段内合法 $i<j$ 最大值。相邻段合并为
+
+$$
+(\max(L_A,L_B),\max(R_A,R_B),\max(B_A,B_B,L_A+R_B)).
+$$
+
+顺序不能交换,因为跨段对只允许左端点在 A、右端点在 B。在线段树上查询双倍环的每个长度 $k$ 窗口,取最小 $B$,再与挂树内部直径取最大。
+
+### 正确性与复杂度
+
+任何可删边必在环上;删后任意最短路要么完全位于一棵挂树,要么由“上行到环根 + 环链唯一段 + 下行”构成,两个候选类别完整。摘要按端点所属左右段分类,三项合并恰覆盖且不重复所有 $i<j$。枚举所有旋转窗口等价于枚举所有删边,故最小值正确。
+
+剥叶和树 DP 为 $O(n)$,$k$ 次区间查询为 $O(k\log k)$,空间 $O(n)$。
+
+### 实现指南、核心伪代码与附件
+
+```text
+peel leaves to mark the unique cycle
+for every cycle root:
+    tree DP -> height[root], global tree_diameter
+walk cycle in order and duplicate it; build weighted coordinates d
+leaf summary at i = (h[i]-d[i], h[i]+d[i], -INF)
+for every cut/start s:
+    cross = query summary on [s, s+k).best
+answer = max(tree_diameter, minimum cross over all s)
+```
+
+[完整 GNU C++17 实现](../../参考实现/09-基环树/CodeForces-835F.cpp)
+
+## 精选例题 2:Subset with Zero Sum
+
+### 来源与题意
+
+- VJudge:[CodeForces-1270G](https://vjudge.net/problem/CodeForces-1270G)
+- 原题:[Codeforces 1270G](https://codeforces.com/problemset/problem/1270/G)
+- 难度:2700;考点:反向建模、函数图有向环、望文生义和
+
+多组数据总 $n\le10^6$。给 $i-n\le a_i\le i-1$,构造一个非空下标集合使对应 $a_i$ 之和为 0;题目保证存在。
+
+<details><summary>Hint 1</summary>区间保证 $1\le i-a_i\le n$。</details>
+<details><summary>Hint 2</summary>令 $f(i)=i-a_i$,每个点恰有一条出边 $i\to f(i)$。</details>
+<details><summary>Hint 3</summary>任取函数图有向环 $i_1\to i_2\to\cdots\to i_k\to i_1$。</details>
+
+### Observations、证明与实现
+
+原约束看似只是在保证某个零和子集存在;真正作用是让 `i-a_i` 始终成为合法下标。沿有向环有 $f(i_t)=i_{t+1}$,所以
+
+$$
+\sum_t a_{i_t}=\sum_t(i_t-f(i_t))
+=\sum_t(i_t-i_{t+1})=0.
+$$
+
+任何有限函数图都至少有一个有向环,因此构造必存在。可用入度剥叶删掉所有非环点,再从任一剩余点沿出边一圈输出。每个输出点互异、集合非空,望文生义式证明总和为 0。时间和空间均为 $O(n)$。
+
+```text
+f[i] = i - a[i]
+peel all indegree-zero vertices in the functional graph f
+start from any remaining vertex and follow f until returning
+output exactly this directed cycle
+```
+
+[完整 GNU C++17 实现](../../参考实现/09-基环树/CodeForces-1270G.cpp)
+
+若上下界不能保证 $f(i)$ 落在 $[1,n]$,建图立即失效;若目标和是常数 $C\ne0$,环的望文生义只会给 0,不能直接调整。
+
+## 扩展与联系
+
+第一题的三元摘要是“有顺序的幺半群”雏形,可替换线段树为双栈滑动队列做到线性;第二题则说明函数图不只用于计数,也可承载望文生义和与势能差。
+
+## 总结与自检
+
+两题共同点是先找“唯一环/必有环”,但后续任务完全不同。为什么第一题摘要合并有方向?第二题的下标范围怎样保证出边合法?
+
+## 课后训练建议
+
+先独立完成第二题,再为第一题手推摘要合并式;第一题用 $n\le9$ 枚举删边和全点对距离对拍。卡在第一题 30 分钟后看 Hint 2--3。

+ 81 - 0
课程/10-高斯消元与CRT/01-高斯消元秩解空间与XOR系统.md

@@ -135,6 +135,87 @@ check contradiction; compare rank with variable count
 
 ## 扩展与联系
 
+## 高阶例题:XOR Neighbors
+
+### 来源信息与题意
+
+- VJudge:[AtCoder-abc366_g XOR Neighbors](https://vjudge.net/problem/AtCoder-abc366_g)
+- 原题:[AtCoder Beginner Contest 366 G](https://atcoder.jp/contests/abc366/tasks/abc366_g)
+- 难度:ABC 600 分;考点:邻接矩阵、XOR 零空间、基向量编码
+
+给定 $1\le N\le60$ 的简单无向图。给每个点 $v$ 写一个 $1\le X_v<2^{60}$ 的整数,使每个非孤立点的所有邻点所写整数 XOR 为 0;判断是否存在并构造。
+
+<details><summary>Hint 1:拆开整数的每个二进制位</summary>
+
+固定一位,令 $x_v$ 表示点 $v$ 在该位是否为 1。邻点 XOR 为 0 等价于邻接矩阵 $A$ 满足 $Ax=0$(在 $\mathbb F_2$ 上)。
+
+</details>
+
+<details><summary>Hint 2:为什么一个零空间向量通常不够</summary>
+
+题目要求每个 $X_v$ 非零;单个非零解可能在某些坐标仍是 0。能否把零空间的多组解分别放在不同二进制位?
+
+</details>
+
+<details><summary>Hint 3:何时某个坐标永远为 0</summary>
+
+求出 $\ker A$ 的一组基。若点 $v$ 在每个基向量上的坐标都为 0,则任意线性组合在该坐标也为 0;反之只要某个基向量覆盖它,就能把对应基编号编码进 $X_v$。
+
+</details>
+
+### Observations
+
+1. 直接给点分配整数看似有巨大值域;按位拆开后,每一位都是同一个齐次 XOR 方程组,数值大小只是允许我们并排放最多 60 个解。
+2. 只问是否有非零零空间不够。例如某个点可能在所有零空间向量中恒为 0,即使其他坐标可以非零,仍违反 $X_v\ge1$。
+3. 枚举所有零空间向量是 $2^{N-rank}$;基已经完整描述每个坐标是否可能为 1。把第 $k$ 个基向量放到整数的第 $k$ 位,就一次性保留了全部覆盖信息。
+4. 零空间维数不超过 $N\le60$,恰好能装入题目允许的 60 位;这是数值上界与代数维数的关键契约。
+
+### Editorial、正确性与复杂度
+
+以图的邻接矩阵 $A$ 为系数矩阵。对每个二进制位 $k$,所有点该位组成列向量 $x^{(k)}$,则点 $v$ 的邻点 XOR 在该位为 $(Ax^{(k)})_v$。所以一个合法位必须属于 $\ker A$;孤立点对应全零行,条件自动成立。
+
+Gauss-Jordan 消元后,每个自由变量取 1、其他自由变量取 0,并回代主元变量,得到零空间基 $b_0,\ldots,b_{d-1}$。构造
+
+$$
+X_v=\sum_{k=0}^{d-1} b_k[v]2^k.
+$$
+
+每一位 $b_k$ 都满足 $Ab_k=0$,故所有非孤立点的邻点 XOR 为 0。若所有 $X_v>0$,范围也满足 $X_v<2^d\le2^{60}$,构造合法。
+
+反过来,若某个 $X_v=0$,说明每个基向量在坐标 $v$ 都为 0。基张成整个零空间,所以任意合法位向量在该坐标都为 0,任何整数构造都只能令该点为 0,不可能有解。因此最后的非零检查既充分又必要。
+
+用 64 位整数存每行,消元时间 $O(N^3/64)$ 的位运算实现(按当前逐行写法可视为 $O(N^3)$、但 $N\le60$),空间 $O(N)$ 个机器字。
+
+### 核心伪代码与参考实现附件
+
+```text
+A = adjacency matrix over GF(2)
+RREF(A); record pivot columns
+
+basis = empty
+for every free column f:
+    vector x has x[f] = 1
+    use RREF rows to recover every pivot coordinate of x
+    append x to basis
+
+for vertex v:
+    X[v] = bitmask of basis indices k with basis[k][v] == 1
+if some X[v] == 0: output No
+else: output Yes and all X[v]
+```
+
+完整 GNU C++17 参考实现:[AtCoder-abc366_g.cpp](../../参考实现/10-高斯消元与CRT/AtCoder-abc366_g.cpp)
+
+### 实现细节、复盘与变式
+
+- 无向边要在邻接矩阵的两个对称位置置 1;题目保证简单图,但用 XOR 写入也能避免重复边被误计两次。
+- `N=60` 时最高使用位为 59,`1ULL<<59` 合法;不要用有符号 `1LL<<60` 处理边界。
+- RREF 行与主元列必须一一对应,构造自由变量基时才能直接恢复主元坐标。
+- 识别信号是“所有邻居的 XOR”而不是路径 XOR;前者直接是邻接矩阵乘向量,后者通常进入势能与环空间。
+- 若整数上界只允许 $k<\dim\ker A$ 位,不能机械放下整组基;那会变成“用至多 $k$ 个零空间向量覆盖所有坐标”的额外组合问题。
+
+## 扩展与联系
+
 [CodeForces-1101G Zero XOR Subset](https://vjudge.net/problem/CodeForces-1101G) 把 XOR 线性无关转成线性基;线性基更适合在线插入和最大 XOR,高斯矩阵更适合显式方程与恢复解。
 
 ## 总结与自检

+ 143 - 0
课程/10-高斯消元与CRT/02-CRT与非互质同余合并.md

@@ -131,6 +131,149 @@ else:
 
 ## 扩展与联系
 
+## 高阶例题:Three Traffic Lights
+
+### 来源信息与题意
+
+- VJudge:[AtCoder-agc050_e Three Traffic Lights](https://vjudge.net/problem/AtCoder-agc050_e)
+- 原题:[AtCoder Grand Contest 050 E](https://atcoder.jp/contests/agc050/tasks/agc050_e)
+- 难度:AGC 1800 分;考点:非互质 CRT、周期信息压缩、分段事件扫描
+
+三盏信号灯同时从绿灯开始。第 $i$ 盏以 $g_i$ 秒绿、$r_i$ 秒红循环,且所有 $g_i,r_i$ 都在 $[1,10^{12}]$。求接下来
+
+$$
+(g_1+r_1)(g_2+r_2)(g_3+r_3)
+$$
+
+秒内三灯同时为绿的总时长,模 $998244353$。
+
+<details><summary>Hint 1:一秒对应三个余数</summary>
+
+令周期 $p_i=g_i+r_i$。时刻 $t$ 绿当且仅当 $t\bmod p_i<g_i$。三个指定余数能由同一个 $t$ 产生,当且仅当它们满足非互质 CRT 的两两兼容条件。
+
+</details>
+
+<details><summary>Hint 2:第 i 个余数不需要保留模 p_i 的全部信息</summary>
+
+余数 $x_i$ 只会与另外两个余数分别比较模 $\gcd(p_i,p_j)$。因此只需保留
+
+$$
+q_i=\operatorname{lcm}(\gcd(p_i,p_j),\gcd(p_i,p_k))
+=\gcd(p_i,\operatorname{lcm}(p_j,p_k)).
+$$
+
+</details>
+
+<details><summary>Hint 3:压缩后一个余数有多少个绿色原像</summary>
+
+对 $0\le x<q_i$,区间 $[0,g_i)$ 中与 $x$ 模 $q_i$ 同余的数有
+
+$$
+w_i(x)=\left\lfloor\frac{g_i}{q_i}\right\rfloor+[x<g_i\bmod q_i].
+$$
+
+</details>
+
+<details><summary>Hint 4:不要枚举压缩后的总周期</summary>
+
+令 $L=\operatorname{lcm}(q_1,q_2,q_3)$。目标变为
+
+$$
+\sum_{t=0}^{L-1}\prod_{i=1}^3 w_i(t\bmod q_i).
+$$
+
+每个 $w_i$ 在一周期内只有一次从“商加一”降到“商”。选一个函数做区间求和,只扫描另外两个函数的变化时刻。
+
+</details>
+
+### Observations
+
+1. 直接枚举时间的周期可能接近三个 $10^{12}$ 数的最小公倍数;直接枚举三组绿色余数更坏。非互质 CRT 的兼容条件只看两两 gcd,这允许先删除每个余数中永远不会被比较的素因子指数。
+2. $q_i$ 不是随意取一个 gcd:它必须同时保留与另外两盏灯比较所需的两个模数,因此是这两个 gcd 的 lcm。更小会合并兼容性不同的余数,更大则没有新增判断信息。
+3. 压缩后的余数并非权重 1。同一个 $x\bmod q_i$ 在原绿色区间里可能有多个原像;`整商 + 一个短前缀` 精确保留了这些重数。
+4. 即使 $L$ 仍很大,$w_i$ 也只是周期方波。把一个方波前缀和公式化,乘积只需在另两个方波的断点间积分。
+5. 三个 $q_i$ 的素因子指数有特殊结构:每个素数的最高指数至少出现在两个 $q_i$ 中。可写成 $q_1=gab,q_2=gac,q_3=gbc$,其中 $a,b,c$ 两两互质。选择最小的 $q_i$ 做区间求和后,另外两者的断点次数由较小的两个因子控制,而不是由 $L$ 控制。
+
+### Editorial
+
+先令 $p_i=g_i+r_i$。三个原余数 $(x_1,x_2,x_3)$ 能由同一时刻产生,当且仅当
+
+$$
+x_i\equiv x_j\pmod{\gcd(p_i,p_j)}
+$$
+
+对每对 $i,j$ 都成立。对第 $i$ 个余数,所有会参与这些条件的信息恰合并为模 $q_i$ 的余数。于是每个压缩后兼容三元组由唯一的 $t\bmod L$ 表示,其绿色原像数为三个权重之积,得到上面的求和式。
+
+设 `base_i=g_i//q_i`、`rem_i=g_i%q_i`,则
+
+$$
+w_i(t)=base_i+[t\bmod q_i<rem_i].
+$$
+
+选 $q_i$ 最小的一盏作为 0 号灯。对另外两盏,权重只在每个周期起点和 `起点+rem` 改变;用两个“下一断点”指针合并扫描。相邻断点 $[l,r)$ 内另外两个权重恒定,而 0 号灯可用前缀公式求和:
+
+$$
+F_i(T)=base_iT+\left\lfloor\frac{T}{q_i}\right\rfloor rem_i
++\min(T\bmod q_i,rem_i),
+$$
+
+所以区间权重和是 $F_i(r)-F_i(l)$。
+
+最后还需恢复题目要求的长区间。每个兼容原余数组在一个 $\operatorname{lcm}(p_1,p_2,p_3)$ 周期中出现一次,而题目区间长度是 $p_1p_2p_3$,故乘上
+
+$$
+\frac{p_1p_2p_3}{\operatorname{lcm}(p_1,p_2,p_3)}.
+$$
+
+实现中可写为
+
+$$
+\gcd(p_1,p_2)\cdot\gcd(\operatorname{lcm}(p_1,p_2),p_3),
+$$
+
+并用 `__int128` 计算中间 lcm,避免乘法溢出。
+
+### 正确性证明
+
+1. **压缩合法且完备。** 非互质 CRT 表明兼容性仅由所有两两 gcd 同余决定;$x_i\bmod q_i$ 同时且恰好决定第 $i$ 个余数在相关两个 gcd 下的值。因此压缩既不会把不兼容三元组误合并,也不会区分兼容性完全相同的三元组。
+2. **权重计数不漏不重。** $[0,g_i)$ 中每个原余数唯一落到一个 $x\bmod q_i$;整周期各贡献一次,剩余 `rem_i` 个余数再贡献一次,故 $w_i$ 正确。三个灯的原像独立选择,乘积就是该压缩三元组对应的绿色原三元组数。
+3. **事件扫描等于完整求和。** 扫描边界包含另外两个权重的每一次变化,故每个半开区间内它们恒定;乘以第一盏灯在该区间的精确权重和,就得到该区间全部项。区间首尾连续、不交叠并覆盖 $[0,L)$。
+4. **恢复次数正确。** 每个兼容原三元组由 CRT 给出模原周期 lcm 的唯一时刻类;在题目长度内恰重复上述倍数。因此最终乘法恰覆盖全部时间,且不重复。
+
+把 $q_i$ 写成 $gab,gac,gbc$ 并选择最小者后,扫描事件数为 $O(a+b)$,其中较大的 $a,b$ 仍不超过 $\sqrt{2\times10^{12}}$ 量级;其余运算为常数次 gcd/lcm。空间 $O(1)$(实现直接生成下一断点,不保存事件表)。
+
+### 核心伪代码与参考实现附件
+
+```text
+p[i] = g[i] + r[i]
+q[i] = lcm(gcd(p[i],p[j]), gcd(p[i],p[k]))
+L = lcm(q[0],q[1],q[2])
+reorder lights so q[0] is minimum
+
+answer_one_period = 0
+left = 0
+while left < L:
+    right = min(next change of w[1], next change of w[2], L)
+    answer_one_period += w[1](left) * w[2](left)
+                         * (prefix_w[0](right) - prefix_w[0](left))
+    left = right
+
+repeat = p[0]*p[1]*p[2] / lcm(p[0],p[1],p[2])
+output answer_one_period * repeat modulo 998244353
+```
+
+完整 GNU C++17 参考实现:[AtCoder-agc050_e.cpp](../../参考实现/10-高斯消元与CRT/AtCoder-agc050_e.cpp)
+
+### 实现细节、复盘与变式
+
+- 原周期最多 $2\times10^{12}$;两数 lcm 即使最终只用于 gcd,也必须先进入 `__int128`。
+- 当 `rem_i=0` 时该权重恒定,不应生成“周期起点与结束点重合”的重复事件。
+- 用半开区间 $[l,r)$ 对应离散的整秒时刻,样例中的区间端点也按这一口径理解。
+- 识别信号是“多个巨大周期,但相容性只比较 gcd 投影”;核心不是直接套 CRT,而是先问 CRT 实际读取了每个余数的哪些信息。
+- 若灯数从 3 增大,压缩仍成立,但交叉方波断点可能无法通过“留下一个前缀和、扫描其余两个”控制;三变量是复杂度证明的重要边界。
+
+## 扩展与联系
+
 CRT 解决的是多个同一未知量的同余交集;高斯消元解决多个未知量的线性系统。模数不是质数时,多变量系统可考虑质因数分解后分别求解再 CRT,但素数幂上的消元仍需处理非单位主元。
 
 ## 总结与自检

+ 103 - 0
课程/10-高斯消元与CRT/03-高阶扩展-XOR路径的环空间.md

@@ -0,0 +1,103 @@
+# 高阶扩展:树势能与 XOR 环空间
+
+## 本节目标
+
+把允许重复边的路径 XOR 全集分解成“树上固定势能差 + 环空间任意线性组合”,并按二进制位统计所有不同可达值,而不是枚举路径。
+
+## 前置知识与课前自测
+
+前置:XOR 线性基、DFS 生成树、线性空间计数。
+
+1. 给根到点的树路径 XOR 为 $d_v$,树上 $u\to v$ 的 XOR 是什么?
+2. 一条非树边 $(u,v,w)$ 产生的环 XOR 是什么?
+3. 一个秩为 $r$ 的 XOR 空间中,某位若能变化,有多少向量该位为 1?
+
+<details><summary>答案</summary>
+
+1. $d_u\oplus d_v$。
+2. $d_u\oplus d_v\oplus w$。
+3. 恰 $2^{r-1}$ 个。
+
+</details>
+
+## 问题动机
+
+允许重复点边后,路径数量无限,但 XOR 只记录每条边经过次数的奇偶。无限走法因此不是更难的搜索,而是在暗示有限维环空间。
+
+## 精选例题:Xor-matic Number of the Graph
+
+### 来源与题意
+
+- VJudge:[CodeForces-724G](https://vjudge.net/problem/CodeForces-724G)
+- 原题:[Codeforces 724G](https://codeforces.com/problemset/problem/724/G)
+- 难度:2600;考点:生成树势能、XOR 线性基、逐位计数
+
+无向图 $n\le10^5,m\le2\times10^5$,边权 $0\le w\le10^{18}$。路径允许重复点和边。对每个 $u<v$,每一种可由某条 $u\to v$ 路径得到的不同 XOR 值 $s$ 形成三元组 $(u,v,s)$;求所有 $s$ 之和模 $10^9+7$。
+
+<details><summary>Hint 1</summary>在每个连通分量任取生成树,求根到每点的 XOR 势能 $d_v$。</details>
+<details><summary>Hint 2</summary>把每条边的 $d_u\oplus d_v\oplus w$ 插入线性基;它们张成所有可绕行环的 XOR。</details>
+<details><summary>Hint 3</summary>固定端点后,可达值集合是仿射空间 $(d_u\oplus d_v)+\operatorname{span}(B)$。</details>
+<details><summary>Hint 4</summary>若基能改变第 b 位,则所有点对各有一半空间元素该位为 1;否则只看 $d_u,d_v$ 该位是否不同。</details>
+
+## Observations 与 Editorial
+
+枚举简单路径已经指数级,允许重复边似乎更大;但同一闭合绕行做两次会 XOR 抵消,所以所有可添加绕行形成 $\mathbb F_2$ 线性空间。
+
+在一个连通分量中任选 DFS 树并令 $d_v$ 为根到 $v$ 的树路 XOR。每条图边给出闭合走法值 $c=d_u\oplus d_v\oplus w$,把全部 $c$ 插入线性基 $B$。任意 $u\to v$ 走法与树路之差是若干闭合走法,故其 XOR 属于
+
+$$
+d_u\oplus d_v\oplus\operatorname{span}(B).
+$$
+
+反过来,每个基本环都可从树路中绕行加入,任意线性组合均可实现,所以该集合完整。
+
+设基秩为 $r$。逐位 $b$:
+
+- 若某个基向量第 $b$ 位为 1,线性函数“取第 $b$ 位”在空间上非零,恰一半即 $2^{r-1}$ 个空间元素使该位为 1;每个无序点对贡献相同数量。
+- 否则空间所有向量该位为 0,该位由 $d_u\oplus d_v$ 固定。若分量中势能该位为 1 的点数是 $c$,恰有 $c(s-c)$ 个无序点对为 1,每对对应全部 $2^r$ 个空间元素。
+
+各连通分量之间无路径,独立求和。
+
+## 正确性与复杂度
+
+树势能分解证明每条可达路径值都在上述仿射空间;基本环绕行证明空间每个值都有实际路径,且线性基只保留不同 XOR 值,不会按不同走法重复计数。逐位计数是对有限线性空间上的非零线性函数二等分,故求和准确。
+
+DFS 与扫描边 $O(n+m)$;每个环值插入 60 位线性基,时间 $O(60m+60n)$,空间 $O(n+m)$。
+
+## 实现指南、核心伪代码与附件
+
+```text
+for each connected component:
+    DFS tree -> potential d[v]
+    basis = empty
+    for each component edge (u,v,w):
+        basis.insert(d[u] xor d[v] xor w)
+    r = basis.rank
+    for bit b in 0..59:
+        if span(basis) can set bit b:
+            count = C(size,2) * 2^(r-1)
+        else:
+            count = ones_in_d[b] * zeros_in_d[b] * 2^r
+        answer += count * 2^b
+```
+
+[完整 GNU C++17 实现](../../参考实现/10-高斯消元与CRT/CodeForces-724G.cpp)
+
+## 扩展与联系
+
+树势能加环空间是无向图 XOR 路径的通用分解;在普通加法最短路中,绕环代价不会按两次抵消,因此不能照搬线性空间模型。
+
+## 实现边界与复盘
+
+- 边权到 $10^{18}$,需无符号 64 位并处理位 0..59。
+- 同一环值从无向边两端扫描两次不会改变线性基,但若改为显式列基本环可省常数。
+- 若路径必须简单,任意环空间组合未必能由一条简单路径实现,本模型失效。
+- 识别信号是“允许重复的 XOR 路径”;这通常是在暗示闭合走法只保留奇偶、进入环空间。
+
+## 总结与自检
+
+为什么固定端点的可达集合是仿射空间而不是线性空间?某一位可变化时为什么恰有一半空间向量为 1?
+
+## 课后训练建议
+
+用 $n\le7$、小边权,在 `(vertex,xor)` 状态图上暴力枚举全部可达值对拍。复盘时必须解释“为什么统计的是不同 s,而不是不同路径”。

+ 100 - 0
课程/11-平衡树/04-高阶扩展-随机输入契约与ODT.md

@@ -0,0 +1,100 @@
+# 高阶扩展:随机输入契约与有序区间树
+
+## 本节目标
+
+理解有序区间树(Ordered Disjoint Tree, ODT/Chtholly Tree)的真实适用条件:它维护等值段,依靠随机数据中的区间赋值压缩段数;能说明其期望性能来源,也能构造对抗数据证明它不是通用平衡树模板。
+
+## 前置知识与课前自测
+
+前置:`std::set`、迭代器失效、快速幂、随机生成器。
+
+1. 把数组压成极大等值段后,区间赋值会让段数怎样变化?
+2. 区间加法会不会合并大量段?
+3. 若操作由题面固定伪随机生成,平均情况假设是否属于输入契约的一部分?
+
+<details><summary>答案</summary>
+
+1. 删除内部所有段并至多新建一个段,通常显著减少段数。
+2. 不会;它只改变覆盖段的值。
+3. 是,但必须严格复现生成器;对任意手工输入不能沿用期望复杂度结论。
+
+</details>
+
+## 问题动机
+
+四类操作无法由常规固定摘要自然统一;题面把操作全部随机生成,是在邀请我们把“等值段数量的期望受控”纳入复杂度分析,而不是寻找一个虚构的最坏情况对数模板。
+
+## 精选例题:Willem, Chtholly and Seniorious
+
+### 来源与题意
+
+- VJudge:[CodeForces-896C](https://vjudge.net/problem/CodeForces-896C)
+- 原题:[Codeforces 896C](https://codeforces.com/problemset/problem/896/C)
+- 难度:2600;考点:等值段集合、split、区间赋值摊还、输入生成契约
+
+$n,m\le10^5$。数组和全部操作由给定线性同余伪随机生成器产生;支持区间加、区间赋值、区间第 $k$ 小、区间幂和模任意 $y$。
+
+<details><summary>Hint 1</summary>四类操作很难由一个固定维度的线段树摘要同时支持;尤其第 k 小与任意指数幂和。</details>
+<details><summary>Hint 2</summary>维护互不相交且覆盖数组的等值段 `(l,r,value)`,按 l 排序。</details>
+<details><summary>Hint 3</summary>`split(pos)` 把包含 pos 的段切成 `[l,pos-1]` 与 `[pos,r]`,并返回后者迭代器。</details>
+<details><summary>Hint 4</summary>对 `[l,r]` 先 split(l)、split(r+1),之后覆盖段在 set 中形成一个连续迭代器区间。</details>
+
+## 从朴素到数据结构
+
+逐点操作最坏 $O(nm)$。线段树能处理区间加/赋值,但“第 $k$ 小”和“任意临时指数、临时模数的幂和”没有小而稳定的合并摘要。题面却保证所有数据与操作按固定随机序列生成,且四种操作均匀出现;频繁的随机区间赋值会删除大量旧断点。
+
+ODT 维护当前等值段。统一切出 `[l,r]` 后:
+
+- 加法:遍历覆盖段并修改 `value`;
+- 赋值:擦除覆盖段,插入单段 `[l,r,x]`;
+- 第 $k$ 小:收集 `(value,length)`,按值排序后扣长度;
+- 幂和:对每段加 `length * value^x mod y`。
+
+单次耗时与被访问段数成正比(第 $k$ 小再乘排序对数)。随机赋值持续把长区间压成一个段,使期望总访问量可接受。
+
+## 正确性证明
+
+不变量是 set 中各段按左端点严格递增、互不重叠、并集为 $[1,n]$,且段内数组值相同。`split` 只把一段拆成两个同值段,不改变数组;之后 `[split(l),split(r+1))` 恰覆盖查询区间。
+
+加法逐段修改与逐元素加等价;赋值删除恰好覆盖范围并插入统一值;查询中每段长度等于该值在区间贡献的元素数。因此四种操作都保持不变量并返回精确答案。
+
+### 复杂度边界
+
+确定性最坏复杂度仍可达 $O(nm)$:交替制造大量单点不同值,再查询整段即可扫描 $\Theta(n)$ 段。这里通过的依据是题目指定的伪随机生成过程与大量随机区间赋值带来的期望段数控制,不可写成“ODT 保证均摊 $O(\log n)$”。
+
+## 实现指南、核心伪代码与附件
+
+```text
+split(pos):
+    find interval [l,r,v] containing pos
+    if l == pos: return it
+    replace it by [l,pos-1,v] and [pos,r,v]
+    return iterator of [pos,r,v]
+
+operate(l,r):
+    end = split(r+1)
+    begin = split(l)
+    # [begin,end) is exactly the affected interval list
+```
+
+[完整 GNU C++17 实现](../../参考实现/11-平衡树/CodeForces-896C.cpp)
+
+## 扩展与联系
+
+ODT 与平衡树的联系只在于用有序集合维护断点;它不维护树形聚合信息。分块也能利用随机赋值重构块,但性能模型与实现边界不同。
+
+## 实现细节、复盘与变式
+
+- 节点按 `l` 比较;`r,value` 必须可通过 const 迭代器修改,常用 `mutable`。
+- 赋值时先取得 `end=split(r+1)`,再取得 `begin=split(l)`,随后整体 `erase(begin,end)`。
+- 必须逐字复现 `rnd()` 的调用次数;第 3 类和其他操作生成 `x` 的取模不同,第 4 类还多生成 `y`。
+- 幂和先把底数模 `y`;本题 `y\le10^9`,64 位乘法足够。
+- 若输入改为用户显式给出的对抗操作,本节实现不再有性能保证,应换带有序统计/值域结构的更强方案。
+
+## 总结与自检
+
+写出一个让段数长期保持 $\Theta(n)$ 的操作序列;说明 `split` 为何不改变语义;解释这题的随机性为何属于复杂度契约。
+
+## 课后训练建议
+
+用小数组逐点模拟对拍四类操作,并额外记录每步段数观察赋值的压缩效果。卡在接口实现时先单测 `split(1)`、`split(n+1)` 与连续赋值。

+ 24 - 10
课程/README.md

@@ -14,17 +14,17 @@
 
 | 顺序 | 主题与子主题 | 真正前置 | 状态 |
 |---|---|---|---|
-| 01 | [欧拉路](01-欧拉路径/01-判定构造与建模.md):判定、Hierholzer、字符串拼接、边定向 | DFS、度数、连通性 | 完成 |
+| 01 | [欧拉路](01-欧拉路径/01-判定构造与建模.md):判定、Hierholzer、字符串拼接、边定向、掩码图欧拉回路 | DFS、度数、连通性 | 完成(共 2 节) |
 | 02 | [强连通与双连通](02-强连通与双连通/01-强连通分量与缩点.md):SCC、缩点、桥、割点、边双/点双 | DFS、栈、树边与返祖边 | 完成(共 3 节) |
-| 03 | [Bitset](03-Bitset/01-字级并行集合模型与动态匹配.md):字级并行、集合运算、可达性与复合数据结构 | 位运算、复杂度估算 | 完成 |
+| 03 | [Bitset](03-Bitset/01-字级并行集合模型与动态匹配.md):字级并行、集合运算、可达性、补集子掩码隐式图 | 位运算、复杂度估算 | 完成(共 2 节) |
 | 04 | [KMP 与 Manacher](04-KMP与Manacher/01-KMP失配边界周期与差分建模.md):失配、border/周期、回文半径与计数 | 字符串下标、前缀思想 | 完成(共 2 节) |
-| 05 | [最短路与类最短路](05-最短路与类最短路/01-建图松弛与01状态图.md):模型选择、状态扩展、最短路树、次短路、差分约束 | 基础图论、堆、松弛 | 完成(共 3 节) |
-| 06 | [最小/大生成树](06-最小与最大生成树/01-割性质与隐式完全图建模.md):边分类、替换边、Kruskal 重构树、XOR MST | 并查集、树上倍增 | 完成(共 3 节) |
-| 07 | [二分图](07-二分图/01-二分染色与构造选择.md):染色、匹配、Kőnig 定理、KM/Hungarian | BFS/DFS、增广思想 | 完成(共 3 节) |
+| 05 | [最短路与类最短路](05-最短路与类最短路/01-建图松弛与01状态图.md):模型选择、状态扩展、周期内进度、差分约束 | 基础图论、堆、松弛 | 完成(共 4 节) |
+| 06 | [最小/大生成树](06-最小与最大生成树/01-割性质与隐式完全图建模.md):边分类、替换边、关联图最大森林、同步缩点 | 并查集、树上倍增 | 完成(共 4 节) |
+| 07 | [二分图](07-二分图/01-二分染色与构造选择.md):染色、匹配、Kőnig 定理、关联图欧拉平衡 | BFS/DFS、增广思想 | 完成(共 4 节) |
 | 08 | [高级 Segment Tree / Fenwick Tree](08-高级线段树与树状数组/01-Fenwick离线计数与序关系.md):离线计数、动态开点、合并/分裂、可持久化、Beats | 已掌握基础树状数组与 Lazy Segment Tree | 完成(共 5 节) |
-| 09 | [基环树](09-基环树/01-剥叶找环与环上统计.md):找环、剥叶、挂树汇总、函数图计数 | 树形 DP、拓扑剥叶 | 完成 |
-| 10 | [高斯消元与 CRT](10-高斯消元与CRT/01-高斯消元秩解空间与XOR系统.md):有限域/XOR、秩与解空间、exCRT | 模运算、扩展欧几里得 | 完成(共 2 节) |
-| 11 | [平衡树](11-平衡树/01-FHQ-Treap按值分裂与顺序统计.md):FHQ Treap 名次与序列、Splay 区间维护 | BST、随机化、Lazy 标记 | 完成(共 3 节) |
+| 09 | [基环树](09-基环树/01-剥叶找环与环上统计.md):找环、剥叶、删环边直径、函数图代数构造 | 树形 DP、拓扑剥叶 | 完成(共 2 节) |
+| 10 | [高斯消元与 CRT](10-高斯消元与CRT/01-高斯消元秩解空间与XOR系统.md):有限域/XOR、零空间覆盖、环空间、非互质周期压缩 | 模运算、扩展欧几里得 | 完成(共 3 节) |
+| 11 | [平衡树](11-平衡树/01-FHQ-Treap按值分裂与顺序统计.md):FHQ Treap、Splay、随机输入契约与 ODT | BST、随机化、Lazy 标记 | 完成(共 4 节) |
 | 12 | [2016–2026 历年真题](12-历年真题/01-2016至2018期望最短路与基环建模.md):图论、字符串/代数、数据结构、综合建模 | 前 11 个主题中对应章节 | 完成(共 10 节、19 道真题) |
 
 ## 历年真题学习路线
@@ -42,9 +42,23 @@
 9. [省选 2025:推箱子与单调位移压缩](12-历年真题/09-省选2025推箱子与单调位移压缩.md):P11833。
 10. [省选 2026:星图与 GF(2) 可达空间](12-历年真题/10-省选2026星图与GF2可达空间.md):P15653,含最小本地协议验证。
 
-## 下一阶段入口
+## 高阶扩展入口与状态
 
-[《CF 2400–2700 高阶扩展路线》](00-CF2400-2700高阶扩展路线.md) 已固定 11 个算法大主题的候选矩阵与深度口径;它属于下一阶段,本轮没有把候选题冒充为已完成讲义。
+[《Codeforces 2400–2700 与精选 AtCoder 高阶扩展路线》](00-CF2400-2700高阶扩展路线.md) 记录正式题、衔接题、灵活性实验室与二次筛选池。正式矩阵只保留已形成“来源核验—分级 Hint—Observations—Editorial/证明—伪代码—C++17 附件—样例或对拍”闭环的题;不按题量把候选冒充成完成内容。
+
+| 主题 | 本轮正式高阶入口 | 状态 |
+|---|---|---|
+| 欧拉路径 | [掩码图欧拉回路](01-欧拉路径/02-高阶扩展-掩码图上的欧拉回路.md),以及主讲义中的 `agc018_f`、`agc032_c` | 完成 |
+| 强/双连通 | [点双连通与圆方树入口](02-强连通与双连通/03-割点点双连通与圆方树入口.md) 中 `agc071_c` | 完成 |
+| Bitset | [补集子掩码隐式图](03-Bitset/02-高阶扩展-补集子掩码隐式图.md) | 完成 |
+| KMP/Manacher | [Manacher](04-KMP与Manacher/02-Manacher回文半径与区间计数.md) 中 `qupc2018_h` | 完成 |
+| 最短路 | [周期内进度状态](05-最短路与类最短路/04-高阶扩展-周期内进度状态.md),以及差分约束讲义中的 `agc056_c` | 完成;`agc062_d` 为灵活性实验室 |
+| 生成树 | [关联图最大森林](06-最小与最大生成树/04-高阶扩展-关联图中的最大森林.md),以及 `agc014_e` | 完成;`agc064_b` 为衔接题 |
+| 二分图 | [关联图欧拉平衡](07-二分图/04-高阶扩展-关联图欧拉平衡.md),以及染色讲义中的 `agc025_d` | 完成 |
+| Segment/Fenwick | [Fenwick 离线计数](08-高级线段树与树状数组/01-Fenwick离线计数与序关系.md) 中 `agc023_e` | 完成 |
+| 基环树/函数图 | [删环边直径与代数构造](09-基环树/02-高阶扩展-删环边直径与代数构造.md) | 完成;`agc062_f` 为研究导读 |
+| 高斯/CRT | [XOR 路径环空间](10-高斯消元与CRT/03-高阶扩展-XOR路径的环空间.md),以及 `abc366_g`、`agc050_e` | 完成 |
+| 平衡树/序列结构 | [随机输入契约与 ODT](11-平衡树/04-高阶扩展-随机输入契约与ODT.md) | 完成 |
 
 ## 完成判据