Prufer序列

image

Prufer序列是一种将带标号的 \(n\) 个节点树用一个唯一的长度为 \(n - 2\) 整数序列表示的方法。
每次选取编号最小的叶子,把他的baba加到Prufer序列里,然后把这个点删了。
(你可能问说有没有别的构造方式,但这是大家公认的构造方式,一般都这么构造)
这里就有一种用堆构造Prufer序列的方式。
即先把所有度为 \(1\) 的点加到堆里(按照编号为值)。
取堆顶(编号最小的),把他的baba加到Prufer序列里,再把当前点删了(对堆进行pop,并把他的baba的度减一),如果他的baba的度被减到 \(1\) 了,就把他的baba加到堆里。
复杂度为 \(\mathcal O(n\log n)\)
还有一种 \(\mathcal O(n)\) 的方法。
看看代码(从OI-Wiki上复制的):

vector<vector<int>> adj;
vector<int> parent;

void dfs(int v) {
  for (int u : adj[v]) {
    if (u != parent[v]) parent[u] = v, dfs(u);
  }
}

vector<int> pruefer_code() {
  int n = adj.size();
  parent.resize(n), parent[n - 1] = -1;
  dfs(n - 1);

  int ptr = -1;
  vector<int> degree(n);
  for (int i = 0; i < n; i++) {
    degree[i] = adj[i].size();
    if (degree[i] == 1 && ptr == -1) ptr = i;
  }

  vector<int> code(n - 2);
  int leaf = ptr;
  for (int i = 0; i < n - 2; i++) {
    int next = parent[leaf];
    code[i] = next;
    if (--degree[next] == 1 && next < ptr) {
      leaf = next;
    } else {
      ptr++;
      while (degree[ptr] != 1) ptr++;
      leaf = ptr;
    }
  }
  return code;
}

因为只删了 \(n - 2\) 个点,最后肯定剩下两个点,其中一个是 \(n\)
它的正确性显然与堆一致。
显然对于一棵树,这么构造的序列是唯一的。
显然,如果一个点在Prufer中出现了 \(k\) 次,那么他在树上的度数为 \(k + 1\)


其实Prufer序列与带标号树构成双射。
所以这里还有一种Prufer序列 \(\to\) 带标号树的算法。
看看代码(也是从OI-Wiki上抄的):

vector<pair<int, int>> pruefer_decode(vector<int> const& code) {
  int n = code.size() + 2;
  vector<int> degree(n, 1);
  for (int i : code) degree[i]++;

  int ptr = 0;
  while (degree[ptr] != 1) ptr++;
  int leaf = ptr;

  vector<pair<int, int>> edges;
  for (int v : code) {
    edges.emplace_back(leaf, v);
    if (--degree[v] == 1 && v < ptr) {
      leaf = v;
    } else {
      ptr++;
      while (degree[ptr] != 1) ptr++;
      leaf = ptr;
    }
  }
  edges.emplace_back(leaf, n - 1);
  return edges;
}
posted @ 2026-06-02 13:00  SigmaToT  阅读(4)  评论(0)    收藏  举报