[CSP-S 2019] 树上的数 题解

[CSP-S 2019] 树上的数 题解

原题链接

题目大意

给定一棵 \(n\) 个点的树,每个点有一个点权,且所有点的点权为 \(1 \sim n\) 的排列 。你必须执行恰好 \(n - 1\) 次操作,每次任选一条没删过的边删掉,并交换该边连接的两个点的点权。最后把编号按点权排序,求字典序最小的编号序列。

一点题外话:听说这道题绝大部分都是从部分分开始启发正解的,但我怎么题都过了都还不知道怎么写部分分?

思路

约定 \(g_u\) 为所有与 \(u\) 相邻的边的集合。

看到字典序,首先想到一个简单的贪心:按值从小到大枚举,每次找到它能移动到的最小的编号(不含自己),即为答案。那么问题就转换成怎么找到一个点能移动到的最小编号。

注意到每条边都必定被删掉(相信不会有人像我一样没有看见吧) ,所以考虑这样一个情景:当我们确定初始在 \(u\) 的点权 \(val\) 在最终会移动到 \(v\) 时,假设 \(u \rightarrow v\) 的路径为 \(u \rightarrow p_1 \rightarrow … \rightarrow p_m \rightarrow v\) ,则边 \(u \rightarrow p_1\) 必定是 \(g_u\) 中第一个被删除的数,记为 \(bg_u\) ;边 \(p_m \rightarrow v\) 必定是 \(g_u\) 中第一个被删除的数,记为 \(ed_u\) 。证明显然(只要没有读错题)。接着观察:边 \(p_{i - 1} \to p_{i}\) (记为 \(in\) )和 \(p_{i} \to p_{i + 1}\) (记为 \(out\) )在 \(g_{p_i}\) 中必然被先后连续删除(即在两条边删除的时刻之间不能删除其它 \(g_{p_i}\) 中的边),否则 \(val\) 就被换走了;同时,由于一条边只能删一次,之后的其它值显然不能再走 \(p_{i - 1} \to p_{i}\) 和 \(p_{i} \to p_{i + 1}\) (但反过来是可以的)。

我们发现所有性质都与边及其方向有关,所以果断对所有边的两个方向编号(对 \(g_u,\ g_v\) 分别编号)。再来考虑上述性质,似乎就更可做了。具体来说,对于一个 \(g_u\) ,我们把边想象成点(\(define\ 点\ 边\)),用 \(bg_u\) 和 \(ed_u\) 记录 \(g_u\) 中第一个与最后一个被删除的点的编号;对于 \(in,\ out\) 这样一对点,我们对它们连“边”,即 \(in \to out\) 。由于上述最后一条性质,每个点至多只有一条出边与一条入边。又由于一条边表示的是先后关系,故不能成环。所以实际上我们是对每个 \(g_u\) 维护了若干条链,每次选择就是将两条链首尾相连。

至此,我们似乎就可以做了,只需要判断亿点点非法情况:

  • 当选择一条边 \(id\) 为 \(bg_u\) 时:

    \[\begin{align} &1.\ u\ 之前存在\ bg_u\\ &2.\ id\ 不是一条链的链头\\ &3.\ id\ 与\ ed_u\ 属于同一条链,且该条链的长度 < deg_u\\ \end{align} \]

  • 当选择一条边 \(id\) 为 \(ed_u\) 时:

    \[\begin{align} &1.\ u\ 之前存在\ ed_u\\ &2.\ id\ 不是一条链的链尾\\ &3.\ id\ 与\ bg_u\ 属于同一条链,且该条链的长度 < deg_u\\ \end{align} \]

  • 当从 \(in \to out\) 时:

    \[\begin{align} &1.\ in = ed_u\\ &2.\ in = bg_u\\ &3.\ in,\ out\ 属于同一条链\\ &4.\ in\ 不为一条链的链尾\\ &5.\ out\ 不为一条链的链头\\ &6.\ 当\ in\ 与\ bg_u\ 属于同一条链,\ out\ 与\ ed_u\ 属于同一条链,\ 且两条链的长度之和<deg_u \end{align} \]

终于,我们理清了本题的思路:按值从小到大枚举,每次 \(dfs\) 找到最小的可达编号,并在路径上作修改,即可。

时间复杂度:\(\mathcal{O}(n^2)\) 。空间复杂度:\(\mathcal{O}(n)\) 。

Code
#include<bits/stdc++.h>
#define LL long long
#define PLL pair<LL, LL>
#define PLI pair<LL, int>
#define PIL pair<int, LL>
#define PII pair<int, int>
using namespace std;
const int N = 2000;
inline void trans(int& x, const int y) { x = (x <= y ? x : y); }
struct DSU {
    int fa[N * 2 + 5], siz[N * 2 + 5], head[N * 2 + 5], tail[N * 2 + 5];
    inline void init(int n) { for (int i = 1; i <= n * 2; i++) fa[i] = i, siz[i] = 1, head[i] = tail[i] = i; }
    int Find(int x) { return (x == fa[x] ? x : fa[x] = Find(fa[x])); }
    inline void Union(int x, int y) {
        x = Find(x); y = Find(y);
        if (x == y) return;
        int h = head[x], t = tail[y];
        if (siz[x] > siz[y]) swap(x, y);
        fa[x] = y;
        siz[y] += siz[x];
        head[y] = h; tail[y] = t;
    }
}dsu;
int n, ecnt, val[N + 5], pos[N + 5], ans[N + 5], bg[N + 5], ed[N + 5];
struct edge {   
    int v, x, y;
};
vector<edge> g[N + 5];
inline void init() {
    dsu.init(n); ecnt = 0;
    for (int i = 0; i <= n + 1; i++) g[i].clear(), bg[i] = ed[i] = -1;
}
int getmin(int u, int in = 0) {
    int ret = n + 1;
    if (ed[u] == -1 && in != 0 && dsu.tail[dsu.Find(in)] == in)
        if (!(dsu.head[dsu.Find(in)] == bg[u] && dsu.siz[dsu.Find(in)] < g[u].size())) ret = u;
    for (auto i : g[u]) {
        int v = i.v, id = i.x;
        if (in == 0) {
            if (id != dsu.head[dsu.Find(id)]) continue;
            if (dsu.tail[dsu.Find(id)] == ed[u] && dsu.siz[dsu.Find(id)] < g[u].size()) continue;
        } else {
            int a = dsu.Find(in), b = dsu.Find(id);
            if (in == ed[u] || id == bg[u] || a == b) continue;
            if (in != dsu.tail[a] || id != dsu.head[b]) continue;
            if (dsu.head[a] == bg[u] && dsu.tail[b] == ed[u] && dsu.siz[a] + dsu.siz[b] != g[u].size()) continue;
        }
        trans(ret, getmin(v, i.y));
    }
    return ret;
}
bool update(const int mi, int u, int in = 0) {
    if (u == mi) return ed[u] = in, true;
    for (auto i : g[u]) {
        int v = i.v, id = i.x;
        if (in == 0) {
            if (id != dsu.head[dsu.Find(id)]) continue;
        } else {
            int a = dsu.Find(in), b = dsu.Find(id);
            if (in == ed[u] || id == bg[u] || a == b) continue;
            if (in != dsu.tail[a] || id != dsu.head[b]) continue;
            if (dsu.head[a] == bg[u] && dsu.tail[b] == ed[u] && dsu.siz[a] + dsu.siz[b] != g[u].size()) continue;
        }
        bool ret = update(mi, v, i.y);
        if (ret) {
            if (in != 0) dsu.Union(in, id);
            else bg[u] = id;
            return true;
        }
    }
    return false;
}

inline void solve() {
    cin >> n; init();
    for (int i = 1; i <= n; i++) cin >> pos[i], val[pos[i]] = i;
    for (int i = 1; i < n; i++) {
        int u, v; cin >> u >> v;
        g[u].push_back({v, ++ecnt, ecnt + 1});
        g[v].push_back({u, ++ecnt, ecnt - 1});
    }
    if (n == 1) return cout << "1\n", void(0);
    for (int i = 1; i <= n; i++) {
        ans[i] = getmin(pos[i]);
        update(ans[i], pos[i]);
    }
    for (int i = 1; i <= n; i++) cout << ans[i] << " "; cout << "\n";
}
signed main() {
    ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
    int tt = 1;
    cin >> tt;
    while (tt--) solve();
    return 0;
}
posted @ 2026-11-12 18:49  千千的咲  阅读(4)  评论(0)    收藏  举报