Loading

树上一些常见的性质

前言

最近做题发现,树上问题有一些性质很重要,所以打算在这总结一下

性质

  • 树上求路径交:

给出两条路径 \((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]\)

待更新……

posted @ 2021-06-12 19:20  Dita  阅读(97)  评论(0编辑  收藏  举报