Prüfer
入门
长度为 \(n - 2\) 的双射,可以构建起数列和无根树的关系。
构建方法 :
- 取 \(\text index\) 最小的叶子,向 Prüfer 加入其父亲,并删除其本身。
大概是非常容易写 \(n \log n\) 的小根堆即可。
摘自 OI-wiki :
vector<vector<int>> adj;
vector<int> pruefer_code() {
int n = adj.size();
set<int> leafs;
vector<int> degree(n);
vector<bool> killed(n, false);
for (int i = 0; i < n; i++) {
degree[i] = adj[i].size();
if (degree[i] == 1) leafs.insert(i);
}
vector<int> code(n - 2);
for (int i = 0; i < n - 2; i++) {
int leaf = *leafs.begin();
leafs.erase(leafs.begin());
killed[leaf] = true;
int v;
for (int u : adj[leaf])
if (!killed[u]) v = u;
code[i] = v;
if (--degree[v] == 1) leafs.insert(v);
}
return code;
}
但是其实可以做到线性构造。
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;
}
大概是考虑暴力删除所有叶子,然后看看父亲行不行。那时间复杂度怎么保证?这个程序是先逐次删除所有叶子节点,那么可以考虑在过程中父亲若 \(\text index\) 较小,那必然会提前删除,否则会留到后面删,故可以保证只会遍历到每一个节点的父亲边到一次,故为 \(O(n)\)。
重构树
那么我们可以考虑先求出 \(\text deg_i\) 那么我们可以找度数为 1 意义下的 \(\text index\) 最小,然后连到 Prüfer 上面即可。
这个可以用小根堆维护,但也有线性方法, 方法大致和上面差不多,直接塴了 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;
}
trick
完全图 \(K_n\) 有 \(n^{n-2}\) 棵生成树。
考虑到每个节点都有边,那么就可以随便生成树,再考虑 Prüfer 有多少种即可。
度数为 \(d_1 \to d_n\) 的无根树方案有 :
种。
CF156D Clues 记得交题。
一个 \(n\) 个点 \(m\) 条边的带标号无向图有 \(k\) 个连通块。我们希望添加 \(k-1\) 条边使得整个图连通。求方案数。
考虑连通块缩成点。
考虑原图联通,不同情况还要乘上:\(\prod size_i ^ {d_i}\)
有点 trash,考虑 \(e_i \to d_i - 1\)。
考虑多项式定理。
然后就是带入有 \((size_1 + size_2 + .... + size_k)^{k - 2}\prod size_i = n^{k - 2} \prod size_i\)
P6086 【模板】Prüfer 序列
P2290 [HNOI2004] 树的计数
P2624 [HNOI2008] 明明的烦恼
考虑到其占有 \(x = \sum d_i - 1\) 这么多位置。那么答案就是 :
大概是这样的。用 py 写。
n = (int)(input());
k = 0;
s = 0;
a = [0 for i in range(n + 5)];
for i in range(1, n + 1) :
a[i] = (int)(input());
if a[i] == 0 : print(0); exit();
if a[i] != -1 : k += 1; s += a[i] - 1;
if s > n - 2 : print(0); exit();
f = [0 for i in range(n + 5)];
f[0] = 1;
for i in range(1, n + 1) : f[i] = f[i - 1] * i;
ans = ans = (f[n - 2] // f[s] // f[n - 2 - s]) * f[s]
for i in range(1, n + 1) :
if a[i] != -1 : ans //= f[a[i] - 1];
print(ans * pow(n - k, n - 2 - s));

浙公网安备 33010602011771号