换根 DP

简介 #

换根 DP 是基于 树形 DP 的更高效算法. 通常设 $f[u]$ 表示以 $u$ 为树根时的解,进而推出 $f[$其它节点$]$.

例 1 #

给定一棵 $n$ 个点的树,请求出一个节点,使得以其为根时,所有节点的深度之和最大(根节点的深度为 $1$).

$d[u]$:(以 $1$ 为树根时)$u$ 的深度.

$\text{size}[u]$:(以 $1$ 为树根时)以 $u$ 为根的子树的节点数.

$f[u]$:以 $u$ 为树根时的节点深度和.

以 $1$ 为根时:

flowchart
1 --> 2 & 3
2 --> 4 & 5
3 --> 6 & 7 & 8

以 $3$ 为根时:

flowchart
3 --> 1 & 6 & 7 & 8
1 --> 2 --> 4 & 5   

观察发现,原先 $3$ 的子树中(包括 $3$)节点的深度减少了 $1$,而其余节点的深度增加了 $1$. 因此

$$f[3]=f[1]-\text{size}[3]+(n-\text{size}[3])$$

进一步,设 $u$ 是 $v$ 的父节点,则

$$f[v]=f[u]-\text{size}[v]+(n-\text{size}[v])$$

先假定 $1$ 为树根,用树形 DP 推出 $\text{size}$ 数组和 $d$ 数组;再用树形 DP 推出 $f$ 数组,并取最大值.

初始条件为 $\displaystyle f[1]=\sum d[i]$,时间复杂度为 $O(n)$.

void dfs1(int u, int fa) {
    size[u] = 1;
    d[u] = d[fa] + 1;
    for (int i = 0; i < g[u].size(); i ++) {
        int v = g[u][i];    // v 是 u 的第 i 个子节点
        if (v == fa) continue;
        dfs1(v, u);
        size[u] += size[v];
    }
}

void dfs2(int u, int fa) {
    for (int i = 0; i < g[u].size(); i ++) {
        int v = g[u][i];
        if (v == fa) continue;
        f[v] = f[u] - size[v] + (n - size[v]);
        dfs2(v, u);
    }
}

int main() {
    dfs1(1, 0);
    for (int i = 1; i <= n; i ++)
        f[1] += d[i];
    dfs2(1, 0);
    for (int i = 1; i <= n; i ++)
        maxn = max(maxn, f[i]);
    cout << maxn;
}