树上一些常见的性质
前言
最近做题发现,树上问题有一些性质很重要,所以打算在这总结一下
性质
- 树上求路径交:
给出两条路径 \((a,b)\) 四个点两两求 \(LCA\), 得到 \(x_1=lca(a,c),x_2=lca(a,d),x_3=lca(b,c),x_4=lca(b,d);\)
从这四个点钟选择两个深度最大的点 \(p_1, p_2\) 若 \(p_1\neq p_2\) 那么一定有交 交点分别为 \(p_1, p_2.\)
若 \(p_1==p_2\) 且 \(p_1\) 的深度小于 \(lca(a,b)\) 或 \(lca(c,d)\) 那么两条路径无交点 否则交点为 \(p_1\).
-
\(RMQ\) 求 \(lca\) :树上任意两点的 \(lca\) 一定是它们欧拉序中两点之间的深度最小的点
-
一棵树,求点 1, 2, 3, 4……多个点的 \(lca\) :求出有相邻两点的 \(lca\) 取深度最小的一个
-
树上判断 \(a\) 到 \(b\),\(c\) 到 \(d\) 两条路径是否相交:如果相交,记 \(x=lca(a,b),y=lca(c,d)\),则必有 \(x\) 在 \(c-d\) 路径上或 \(y\) 在 \(a-b\) 路径上
-
对于树上的每一个点,计算其所有子树中最大的子树节点数,这个值最小的点就是这棵树的重心。
-
删除重心后所得的所有子树,节点数不超过原树的1/2,一棵树最多有两个重心,这两个重心的最大子树大小相同;
-
删除树的重心之后,保证分得的 \(max(两个子树 siz)\) 最小
// 这份代码默认节点编号从 1 开始,即 i ∈ [1,n]
int size[MAXN], // 这个节点的“大小”(所有子树上节点数 + 该节点)
weight[MAXN], // 这个节点的“重量”
centroid[2]; // 用于记录树的重心(存的是节点编号)
void GetCentroid(int cur, int fa) { // cur 表示当前节点 (current)
size[cur] = 1;
weight[cur] = 0;
for (int i = head[cur]; i != -1; i = e[i].nxt) {
if (e[i].to != fa) { // e[i].to 表示这条有向边所通向的节点。
GetCentroid(e[i].to, cur);
size[cur] += size[e[i].to];
weight[cur] = max(weight[cur], size[e[i].to]);
}
}
weight[cur] = max(weight[cur], n - size[cur]);
if (weight[cur] <= n / 2) { // 依照树的重心的定义统计
centroid[centroid[0] != 0] = cur;
}
}
-
树上两点路径 = 两点深度和 - 两点 lca 的深度
-
求树上所有点对之间的路径和:考虑每条边对答案的贡献,这条边把树分成两棵子树 u, v,总的代价就是 siz[u] * siz[v] * (这条边的权值)
-
树的直径
void dfs(int u, int fa) {
for (int i = head[u]; ~i; i = e[i].nx) {
int v = e[i].v;
if (v == fa) continue;
dfs(v, u);
ans = max(ans, f[u] + f[v] + e[i].w);
f[u] = max(f[u], f[v] + e[i].w);
}
}
-
树的中心(直径的中点):每个点到直径两个端点的距离 \(dis_1\) 和 \(dis_2\) ,其中 \(min(max(dis_1, dis_2))\) 最小那个点就是中点。
-
\(n\) 个点 \(n\) 条边的基环树,找环上的两个点:用并查集维护,当加入一条边时,两个端点已经连通了,那么这两个点一定时环上的点。
-
树是个二分图,并且树的直径的两个端点一定分别位于二分图两个不同的集合内。
-
求树上两点的距离:差分。每个点存根到该点的距离,\(x\) 到 \(y\) 的距离等于 \(dis[x] + dis[y] - 2 * dis[lca]\)
待更新……