可以用来做一些树上计数问题,相当于一个转化工具。主要用在生成树的问题上。
定义
考虑一棵无根树 \(\mathrm T\),定义度数为 \(1\) 的节点为叶子节点,令叶子节点集合为 \(S\)。每次找到 \(S\) 中编号最小的元素,并把 它连接的那个节点 加入到序列末端,并把这个元素删去,直到只剩两个元素为止。
那么现在显然可以使用堆来维护 \(S\),可以做到 \(O(n \log n)\) 构造。
但事实上,有更优秀的 \(O(n)\) 做法。
考虑从 \(1\) 到 \(n\) 扫描,假设现在遍历到点 \(u\),且满足 \(1, 2, ...., u - 1\) 都不是叶子节点 ,有两种情况:
-
\(u\) 不是叶子节点:直接跳过即可。
-
\(u\) 是叶子节点:设它连接的那个节点为 \(fa_u\),把 \(fa_u\) 加入序列中,把 \(u\) 删去。然后考虑更新 \(S\),不难发现它只影响了 \(fa_u\)。假如 \(fa_u < u\) 且 \(fa_u \in S\),那么重复这个过程,并且再考虑 \(fa_{fa_u}\),直到不满足上面的条件。
不难发现,这个过程中,每个点至多被访问 \(2\) 次,且 \(u\) 单调递增,因此时间复杂度为 \(O(n)\)。
qwq
for(int i = 1; i <= n - 1; i++){
if(tot == n - 2) continue;
if(deg[i] == 1){
ans[++tot] = fa[i]; deg[fa[i]]--; int p = fa[i];
while(deg[p] == 1 && p < i && tot < n - 2) ans[++tot] = fa[p], deg[fa[p]]--, p = fa[p];
}
}
性质
1. \(\mathrm {Prufer}\) 序列的长度为 \(n - 2\),点 \(i\) 在 \(\rm Prufer\) 序列中出现 \(deg_i - 1\) 次(\(deg_i\) 为点 \(i\) 的度数)。
证明非常显然。
应用: 当我们在用 \(\rm Prufer\) 序列还原树的时候,可以还原出每个节点的度数,从而知道叶子节点集合,于是就知道了每个点在 \(\rm Prufer\) 序列中出现时,另一个节点是哪一个叶子,实现与上面那个相似。
2. 每个 \(\rm Prufer\) 序列与一个有标号无根树唯一对应,即 \(\rm Prufer\) 序列与有标号无根树 构成双射。
证明:从构造过程中可以发现,一个 \(\rm Prufer\) 序列唯一对应一棵树,而每棵树都有唯一的合法的 \(\rm Prufer\) 序列。
3. 由 \(n\) 个点组成的有标号无根树的个数一共有 \(n^{n - 2}\) 个。
证明: 由于有标号无根树与 \(\rm Prufer\) 序列构成双射,因此可以直接对 \(\rm Prufer\) 序列进行计数。由于每个 \(\rm Prufer\) 序列都是合法的,所以前面 \(n - 2\) 位置随便放都可以。
例题:
CF156D Clues
首先把每个联通块看成一个大点,这样由 \(\rm Prufer\) 序列的性质,树的形态为 \(n^{k - 2}\),然后不难发现,假如我们随便钦定一个点为根,可以把每条边都归到儿子处,这样每个属于每个联通块的边都只有一条了,方案树为 \(\prod s_i\),\(s_i\) 为联通块大小。
CF917D Stranger Trees
首先无脑二项式繁衍,转化为求钦定的数量。假设现在钦定了 \(k\) 条边,那么就会有 \(n - k\) 个联通块,这下就转化为了求 \(n - k\) 个联通块连成一颗树的方案树了。同上一道题,方案数为 \(n^{n - k - 2} \prod s_i\)。容易直接树形 \(\rm DP\) 做到 \(O(n^3)\)。但是不妨考虑 \(\prod s_i\) 原来的意义,其实就是在每个联通块中选一个点的方案数,直接 \(f_{u, j, 0/1}\) 设点 \(u\) 子树内,选了 \(j\) 个联通块,且 \(u\) 的联通块中是否已经选了点,树上背包合并即可。
qwq
#include<bits/stdc++.h>
#define ll long long
#define pb emplace_back
using namespace std;
const int N = 500 + 10;
const ll mod = 1e9 + 7;
void ADD(ll& x, ll y){x += y; (x >= mod) ? x -= mod : 0;}
int n, siz[N];
ll f[N][N][2], jc[N], jcinv[N], lstf[N][N][2];
vector<int> vec[N];
ll qpow(ll x, int y){
ll ret = 1; if(y < 0) x = qpow(x, mod - 2), y = -y;
for(; y; y >>= 1, x = x * x % mod) if(y & 1) ret = ret * x % mod;
return ret;
}
ll C(int x, int y){
return jc[x] * jcinv[x - y] % mod * jcinv[y] % mod;
}
void dfs(int u, int fa){
f[u][1][0] = f[u][1][1] = 1; siz[u] = 1;
for(auto v : vec[u]){
if(v == fa) continue;
dfs(v, u);
for(int i = 1; i <= siz[u]; i++) lstf[u][i][0] = f[u][i][0], lstf[u][i][1] = f[u][i][1], f[u][i][1] = f[u][i][0] = 0;
for(int i = 1; i <= siz[u]; i++){
for(int j = 1; j <= siz[v]; j++){
ADD(f[u][i + j][0], lstf[u][i][0] * f[v][j][1] % mod);
ADD(f[u][i + j][1], lstf[u][i][1] * f[v][j][1] % mod);
ADD(f[u][i + j - 1][0], lstf[u][i][0] * f[v][j][0] % mod);
ADD(f[u][i + j - 1][1], (lstf[u][i][1] * f[v][j][0] % mod + lstf[u][i][0] * f[v][j][1] % mod) % mod);
}
} siz[u] += siz[v];
}
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
cin >> n;
for(int i = 1; i < n; i++){
int x, y; cin >> x >> y;
vec[x].pb(y); vec[y].pb(x);
}
dfs(1, 0); jc[0] = jcinv[0] = 1;
for(int i = 1; i <= n; i++) jc[i] = jc[i - 1] * i % mod;
jcinv[n] = qpow(jc[n], mod - 2);
for(int i = n - 1; i > 0; i--) jcinv[i] = jcinv[i + 1] * (i + 1) % mod;
for(int i = 0; i < n; i++){
ll ans = 0;
for(int j = i; j <= n; j++) ADD(ans, (((j - i) & 1) ? mod - 1 : 1ll) * C(j, i) % mod * f[1][n - j][1] % mod * qpow(n, n - j - 2) % mod);
cout << ans << " ";
}
return 0;
}**