1 条题解

  • 0
    @ 2026-10-27 6:55:30

    思路与算法

    把每条边的交换看作一个事件。某顶点初始的数字由该点最早发生的事件带走;从边e进入该点的数字,若e是最后事件就停在该点,否则从紧接e的下一事件出去。因此只需给各点的关联边安排一个局部全序。已固定数字的路径会强制某些局部事件连续,并固定局部首事件或尾事件。

    依数字1到n处理。它的初始位置就是输入pos[i],不是排列的逆。树上起终点间路径唯一,从起点DFS所有仍可扩展的路径,在可停位置中取最小编号。经过一处中间顶点时,要求进入事件尚无后继、出去事件尚无前驱,且连接不会成环;若连接含首链和尾链,也不能把尚未接入的其他链封在外面。起点相应检查新首事件,终点检查新尾事件。并查集维护强制连续链,cnt记录该点尚有几条链。选定最小终点后,把路径的首、连续及尾约束永久写入,再处理下一个数字。n=1直接输出1。

    正确性说明

    单个顶点的可扩展性:强制连续约束构成若干互不相交的有向链。没有环、首链无前驱、尾链无后继,且未过早合并首尾链时,可以把剩余链以任意顺序接起来,得到满足所有约束的全序;程序的连接、起始和停止条件正是这些必要充分条件。

    全局可实现性:把有共同端点的边事件按局部全序定向。树的线图由对应各原顶点的团粘接而成,不同团仅在割点相接。一个最短有向环若跨团,必须重复经过团间割点,矛盾;若完全处在一个团,又与局部全序无环矛盾。因此事件依赖无环,存在全局拓扑序,能按此顺序交换并删除每条边。局部连续关系保证已确定数字沿指定路径行走。

    DFS枚举所有满足局部必要充分条件的路径,所以得到的终点都有完整删除顺序可扩展,也不会遗漏任何可扩展终点。选择其中最小者,保留此前较小数字的终点,符合字典序逐位最小原则。对数字编号归纳,最终答案就是所有合法删除顺序中最小的位置序列。

    复杂度

    每组初始化邻接表、事件数组和并查集O(n)。每个数字对树做一次DFS并固化一条至多n-1边的路径,总计O(n²α(n))时间;找根采用路径压缩。所有数组、显式DFS栈和路径均为O(n)空间,多组顺序处理。

    易错点

    pos[i]表示数字i的初始位置;输出也按数字编号,而非按顶点编号。局部事件在边的两个端点使用不同有向编号;反向编号用异或1。不能只检查并查集是否成环,还要避免在其他链未接入时提前连通首尾链。树深可到n-1,使用显式栈。数据为自编训练数据与公开样例,不是官方原始测试数据。

    参考代码(C++11)

    #include <bits/stdc++.h>
    using namespace std;
    struct Solver {
        int n;
        vector<vector<int>> g;
        vector<int> to, fa, prev, next, head, tail, cnt;
        int find(int x) {
            while (fa[x] != x) {
                fa[x] = fa[fa[x]];
                x = fa[x];
            }
            return x;
        }
        bool endOK(int u, int e) {
            return tail[u] < 0 && (head[u] < 0 || find(e) != find(head[u]) || cnt[u] == 1);
        }
        bool joinOK(int u, int e, int f) {
            if (prev[f] >= 0 || f == head[u] || find(e) == find(f))
                return false;
            return !(head[u] >= 0 && tail[u] >= 0 && find(e) == find(head[u]) &&
                     find(f) == find(tail[u]) && cnt[u] > 2);
        }
        void run() {
            cin >> n;
            vector<int> pos(n);
            for (int &v : pos) {
                cin >> v;
                --v;
            }
            g.assign(n, {});
            to.clear();
            for (int i = 1, u, v; i < n; i++) {
                cin >> u >> v;
                --u;
                --v;
                int e = to.size();
                to.push_back(v);
                to.push_back(u);
                g[u].push_back(e);
                g[v].push_back(e + 1);
            }
            if (n == 1) {
                cout << 1 << '\n';
                return;
            }
            int m = to.size();
            fa.resize(m);
            iota(fa.begin(), fa.end(), 0);
            prev.assign(m, -1);
            next.assign(m, -1);
            head.assign(n, -1);
            tail.assign(n, -1);
            cnt.resize(n);
            for (int u = 0; u < n; u++)
                cnt[u] = g[u].size();
            vector<int> par(n), pe(n);
            vector<pair<int, int>> stack;
            vector<int> path;
            for (int s : pos) {
                fill(par.begin(), par.end(), -1);
                stack.clear();
                stack.push_back({s, -1});
                int dest = n;
                while (!stack.empty()) {
                    int u = stack.back().first, e = stack.back().second;
                    stack.pop_back();
                    if (e >= 0 && endOK(u, e))
                        dest = min(dest, u);
                    for (int f : g[u]) {
                        int v = to[f];
                        if (v == par[u])
                            continue;
                        bool ok = e < 0 ? (prev[f] < 0 && f != head[u] &&
                                           (tail[u] < 0 || find(f) != find(tail[u]) || cnt[u] == 1))
                                        : (next[e] < 0 && tail[u] != e && joinOK(u, e, f));
                        if (ok) {
                            par[v] = u;
                            pe[v] = f;
                            stack.push_back({v, f ^ 1});
                        }
                    }
                }
                assert(dest < n);
                path.clear();
                for (int u = dest; u != s; u = par[u])
                    path.push_back(pe[u]);
                reverse(path.begin(), path.end());
                head[s] = path[0];
                for (size_t i = 1; i < path.size(); i++) {
                    int e = path[i - 1] ^ 1, f = path[i], u = to[path[i - 1]];
                    next[e] = f;
                    prev[f] = e;
                    fa[find(e)] = find(f);
                    --cnt[u];
                }
                tail[dest] = path.back() ^ 1;
                cout << dest + 1 << ' ';
            }
            cout << '\n';
        }
    };
    int main() {
        ios::sync_with_stdio(false);
        cin.tie(nullptr);
        int T;
        cin >> T;
        while (T--) {
            Solver s;
            s.run();
        }
    }
    
    • 1

    信息

    ID
    2042
    时间
    2000ms
    内存
    256MiB
    难度
    省选
    标签
    递交数
    0
    已通过
    0
    上传者