一些树上问题

树的直径

定义:树上任意两点之间的最长路径。求法一般是下面两种:

  1. 树形 dp

    树形 dp 子树到根最远距离和子树直径。转移新考虑以子树根为 lca 的长路径。

  2. 两次 dfs

    任选一个点 \(o\) 离其最远的点一定可以是直径一端


trick:对于一棵树上的点集 \(S_1/S_2\) 其直径为 \((p_1,q_1)/(p_2,q_2)\),那么 \(S_1\cup S_2\) 的直径端点一定可以是 \(p_{1/2},q_{1/2}\) 之二。

给定树 \(T_1/T_2\),要求最大的 \(dis_1(u,v)+dis_1(u,w)+dis_2(v,w)\)\(n\leq 2\times 10^5\)

直径的主体应该是 \(dis_2(v,w)\),那么再考虑枚举 \(u\),那么 \(dis_1(u,v)/dis_1(u,w)\) 就可以转化成 \(T_2\) 悬挂的叶子结点。

\(u\) 转变成 \(T_1\) 上的相邻点其 \(dis_1(u,x)\) 贡献很好维护,dfn 上做区间加减即可。我们可以用那个 trick 直接在 \(T1\) 的 dfn 区间上面维护直径长度,线段树复杂度,结论正确性可以往死里分讨是容易证明但有点麻烦的。


trick:链挂点的树最小化挂点直径想要枚举一个端点可以通过分讨简单的处理掉另一个端点的交叉贡献。(这么说好像有点抽象看着题吧 w)

\(n\) 个格子 \(a_1,\dots,a_n\),第 \(i\) 个格子填 \(j\in[1,k]\) 的代价是 \(F_{i,j}\),给定 \(X\),最小化 \(\max_{i\neq j} F_{i,a_i}+F_{j,a_j}+|i-j|\times X\),其中 \(n\leq 10^5,k\leq 15\)

这种东西肯定得枚举 \(i(a_i)\) 的,不然直接做这个二元问题有点过于猛了,但是发现不好处理 \(j_1,j_2\) 的贡献反而更大的问题。

首先先把这个放到树上,建立虚链 \(p_1-p_2-...-p_{n-1}-p_n\),边权为 \(X\),然后 \(a_i=v\) 就是在 \(p_v\) 上面以 \(F_{i,v}\) 为权挂个点,现在要求悬挂点直径最小值。我们可以通过分讨重心的位置降低难度,分别考虑中心的位置在 \(p_v\to o_i:F_{i,v}\) 上还是在 \(p_w\to p_{w+1}:X\) 上面。

  • 在悬挂边上

    枚举 \(F_{i,v}\) 有贡献,那么对于 \(j\) 应该有 \(F_{j,a_j}+|a_i-a_j|\times X\leq F_{i,v}\),那么 \(j_1,j_2\) 造成的贡献一定不会有以 \(i\) 为端点的最长链大,我们可以对于每一个 \(j\) 单独求解最优的 \(a_j\),预处理对于 \(x\gets a_i\) 的最好 \(a_j\) 即可。

  • 在虚链边上

    割断这条边,设距离 \(p_w/p_{w+1}\) 最远的距离为 \(L/R\),那么应该满足 \(|L-R|\leq X\),枚举 \(w\),之后可以排序双指针一直推合法的区间,判一下每个 \(j\) 都有然后边界合法。

这个其实还是挺妙妙的。


树的重心

定义:「树上删除后剩余部分最大连通块最小的点」、「最大连通块都不超过 \(\frac{n}{2}\) 的点」「到别的点的距离和最小的点」的。 其实这三个等价(?

树有一个或者两个相邻的重心。

添加/删除一个叶子重心最多移动一条边。

两棵树接在一起新的重心在两个旧重心的路径上。


trick:子树重心在重链上。

给定一颗大小为 \(n\) 的点边都有权的树,找点对 \((A,B)\) 最小化 \(v_p\times \min(dis_{p,A},dis_{p,B})\),其中 \(n\leq 5\times 10^5\)

显然可以枚举分界线然后分别考虑子树内外重心的贡献。子树内的重心只要一直往上提就可以了,是好做的,其实是重链上第一个其子树大小到 \(\frac{siz}{2}\) 这个界限的点,暴力往上移动就是 \(O(n)\) 的了。

子树外的因为删去这个子树可能影响重链所以没那么好确定,我们可以钦定原本的重心为根,这样子子树外重心要不然在原树重链上要不然在次重链上,在对应链上二分求解即可。


Prufer 序列

定义:长度为 \(n-2\) 的序列,可以看做编号为 \(1,\dots,n\) 的无根树的双射。

建立方式:每次选择一个编号最小的叶子结点并删除,将其连接的结点加入序列,只剩两个节点时停止。 做的时候可以钦定 \(n\) 为根。只要到 \(n-2\) 有两个好处,一是后面两个数一定是 \(n/0\) 不能提供信息,二是不算这两个方便直接用 prufer 序列算结点度数,那结点度数应该是在 prufer 序列中的 \(出现次数 +1\),而最后两个点应该指向一个虚根。

  • \(O(n)\)\(\to\) Prufer 序列:钦定 \(n\) 为根,动态记录度数,枚举 \(p_0=1,\dots,n\),如果是叶子就删除,如果其父亲变成叶子,若 \(fa_p<p_0\) 那么肯定直接接着删除,如果不是叶子,那么就等未来枚举到别的 \(p_0\) 的时候做。
up(i,1,n-1) {
	cin >> fa[i];
	++dep[fa[i]];
}
up(i,1,n-1) {
	int o=i;
	while(o<=i&&!dep[o]) {
		o=fa[o], --dep[o];
		if(++cnt<=n-2) cout << o << ' ';
	}
}
  • \(O(n)\) Prufer 序列 \(\to\) 树:先初始化度数为 \(出现次数 +1\) ,动态记录度数,叶子结点中编号最小的父亲是 prufer 序列的下一位。这个可以跟上面那个同一种形式地去维护,枚举 \(p_0=1,\dots,n\),如果父亲的儿子都确定了也就是在 prufer 中被删除了,若 \(fa_p<p_0\) 那么直接再取下一位作为父亲,那么就等未来枚举到别的 \(p_0\) 的时候做。
up(i,1,n-2) {
	cin >> pru[i];
	++dep[pru[i]];
}
up(i,1,n-1) {
	int o=i;
	while(o<=i&&!dep[o]) {
		if(++cnt<=n-2) fa[o]=pru[cnt];
		o=fa[o], --dep[o];
	}
}
up(i,1,n-1) if(!fa[i]) fa[i]=n;

Caylay 公式:\(n\) 个点可以组成 \(n^{n-2}\) 棵生成树,因为跟 Prufer 序列是双射。

钦定点度数的生成树个数是 \(\frac{(n-2)!}{\prod(d_i-1)!}\)。使用这个双射关系容易得到。

\(n\) 个连通块大小分别为 \(a_1,\dots,a_n\),连成一颗树的方案数是 \(\prod a_i\times (\sum a_i)^{n-2}\)。分配连通块度数 \(d_1,\dots d_n\),那么答案显然是 \(\sum_{\sum d_i=2n-2} \frac{(n-2)!}{\prod(d_i-1)!}\times \prod a_i^{d_i}\),前面连通块分布的方案数,后面是向外连接的点的方案数,这个调整一下可以用多元二项式定理,其实就是 \((\sum a_i)^x\) 的展开,\(Ans=\prod a_i\times \sum_{\sum (d_i-1)=n-2}\frac{(n-2)!}{\prod (d_i-1)!}\times \prod a_i^{d_i}=\prod a_i\times (\sum a_i)^{n-2}\) 这样啦 >_<


CF917D

给定 \(n\) 个点的生成树,求有多少个跟其有恰好 \(0,\dots,n-1\) 条公共边的树,\(n\leq 100\)

\(f_i/g_i\) 表示恰好/钦定 \(i\) 条边相同的方案数,\(f\) 难求 \(g\) 好求,二项式反演 \(f_i=\sum_{j=i}^{n-1}g_j\times \binom{j}{i}\times (-1)^{j-i}\) 后面我们只用考虑怎么求 \(g\)。首先 \(m\) 个大小为 \(a_1,\dots,a_m\) 的连通块的方案数是 \(\prod a_i\times n^{m-2}\) 这样子,所以我们要求的其实是 \(n^{i-1}\times \sum_{a_1,\dots,a_{i+1}} \prod_{i=1}^{i+1} a_i\),其实可以设计 \(f_{i,j,k}\) 表示子树 \(i\) 钦定了 \(j\) 条边最上面的连通块大小为 \(k\) 的方案数,但是这样子 \(O(n^5)\),可以考虑 \(\sum \prod a_i\) 的组合意义,在每个连通块里面选一个的方案数,那么可以设 \(f_{i,j,0/1}\) 表示子树 \(i\) 钦定了 \(j\) 条边 有没有在连通块中选择 的方案数,树形 dp 转移即可。


虚树

定义:能表示无根树点集树关系的最小树。

虚树儿子个数 \(\geq 2\) 的点不超过 \(|S-1|\) 个。可以按照 dfs 序归纳,也可以想象一个深搜,一个虚点相当于对原点集做了一个划分,而划分次数 \(\leq |S-1|\)

建立方式:将 \(S\) 中的点按照 dfn 排序,原点和相邻点 lca 的并集就是虚树点,证明容易想象。


虚树原树连通块的大小的两倍等于按 dfs 序排序后相邻两个关键点的距离之和(开头结尾也算相邻)。容易发现一条边在这个对应路径上走过两次,因为过这条边相当于从子树外跨入或者从子树能跨出,这种情况一定有,且别的情况一定不会过这个边。

posted @ 2025-01-23 19:55  Hypoxia571  阅读(44)  评论(0)    收藏  举报