Prufer 序列

发布时间 2023-08-26 21:52:31作者: _maze

Prufer 序列实际上是一种转化的产物,这种转化使得一棵有 \(n\) 个点的无根树可以在线性时间内与一个有 \(n-2\) 个元素,且序列中元素权值在 \([1,n]\) 中的序列互相转化。

它与单纯的父节点组成的序列区别在于:对于每棵树,它的 Prufer 序列是唯一的,每一个 Prufer 序列也对应着唯一一棵树。换句话说,树与 Prufer 序列是双射关系。

Prufer 序列强大的地方就在这里。把一棵树转化为一个序列,而序列的计数难度明显小于树的计数。

由树转化成 Prüfer 序列

先给出转化的步骤:

  1. 寻找到当前最小的叶子节点 \(u\)
  2. \(u\) 的父亲加入 Prüfer 序列。
  3. 从树上删去 \(u\)
  4. 重复前三个步骤,直到 Prüfer 序列有 \(n-2\) 个数。此时原树必定只剩下两个点。

一个显然的想法是使用小根堆,时间复杂度为 \(O(n\log n)\)。但是在这里我们有线性做法:

  1. 找到当前最小叶子节点 \(u\)
  2. \(u\) 的父亲 \(fa\) 加入 Prüfer 序列,并将 \(fa\) 的度数减少 \(1\)
  3. 如果 \(fa\) 的度数变为 \(1\),且 \(fa<u\),那么将 \(fa\) 的父亲 \(h\) 加入 Prüfer 序列。因为此时 \(fa\) 必定为最小叶子(\(u\) 本来为最小叶子,但是 \(fa\)\(u\) 还小。在删去 \(u\) 的过程中没有除 \(fa\) 以外的新叶子产生,所以 \(fa\) 为最小叶子)。
  4. 重复第三步,每次都跟 \(u\) 比较。直到有一个不符合条件。此时增加 \(u\)
  5. 重复前四步,直到 Prüfer 序列有 \(n-2\) 个数,此时原树必定只剩下两个点。
for (int i = 1; i <= n; ++i) deg[fa[i]]++;

static int check[N], tot;

for (int i = 1; i <= n; ++i) if (deg[i] == 0) check[i] = 1;

for (int mn = 1; mn < n; ++mn) {

  int u = mn;

  while (check[u] == 1) {

	p[++tot] = fa[u];

	if (tot == n - 2) break;

	check[u] = 0;deg[fa[u]]--;

	if (deg[fa[u]] == 0) {

	  check[fa[u]] = 1;

	  if (mn > fa[u]) u = fa[u];

	}

  }

  if (tot == n - 2) break;

}

Prüfer 序列的性质

  1. Prufer 序列中每个点出现的次数代表它有几个儿子,即度数 \(+1\)

由 Prüfer 序列还原树

先给出还原步骤:

  1. 找到当前最小的,不在序列中的点 \(u\),序列中的下标最小点 \(v\) 即为 \(u\) 的父亲。
  2. 删除序列中的第一个 \(v\)
  3. 重复以上步骤,最后会留下最大点和一个点 \(u\)。在一般题目里我们不妨设最大点为无根树的假想根,那么最后的步骤是将 \(u\) 连向最大点。

类似于树转 Prüfer 序列,我们也有线性方法:

  1. 找到当前最小的,不在序列中的点 \(u\),序列中下标最小的点 \(v\) 即为 \(u\) 的父亲。
  2. 删除序列中的第一个 \(v\),如果 \(v\) 删除后不再在序列中出现,且 \(v<u\),那么 \(v\) 的父亲即为序列中下标最小的点的父亲。
  3. 重复步骤二,每次与 \(u\) 比较,直到有一个不符合条件。此时增加 \(u\)
  4. 重复以上步骤,直到 Prüfer 序列为空。
for (int i = 1; i <= n - 2; ++i) deg[p[i]]++;

static int tot;

for (int mn = 1; mn < n; ++mn) {

  int u = mn;

  while (deg[u] == 0) {

	fa[u] = p[++tot];

	deg[u] = 1;

	if (tot == n - 2) break;

	deg[fa[u]]--;

	if (deg[fa[u]] == 0) if (mn > fa[u]) u = fa[u];

  }

  if (tot == n - 2) break;

}

for (int i = 1; i < n; ++i) if (fa[i] == 0) fa[i] = n;