洛谷 P3478:[POI 2008] STA-Station ← 换根DP

【题目来源】
https://www.luogu.com.cn/problem/P3478

【题目描述】
给定一个 n 个点的树,请求出一个结点,使得以这个结点为根时,所有结点的深度之和最大。
一个结点的深度之和定义为该节点到根的简单路径上边的数量。

【输入格式】
第一行有一个整数,表示树的结点个数 n。
接下来(n-1)行,每行两个整数 u,v,表示存在一条连接 u,v 的边。

【输出格式】
输出一行一个整数表示你选择的结点编号。如果有多个结点符合要求,输出任意一个即可。

【输入样例】
8
1 4
5 6
4 5
6 7
6 8
2 4
3 4

【输出样例】
7 或 8

【数据范围】
对于全部的测试点,保证1≤n≤10^6,1≤u,v≤n,给出的是一棵树。

【算法分析】
● 换根 DP 是树形 DP 的一种重要技术,用于解决需要以树中‌不同节点为根‌分别计算答案的问题。其核心思想是在一次动态规划后,通过‌推导出换根时的状态转移公式‌,高效地计算出所有节点作为根时的结果,避免对每个根节点都进行一次 O(n) 的树形DP(那样总复杂度为 O(n²))。

● 对于大多数简单的树形 DP 问题,如计算子树大小、节点深度、简单路径统计等,算法通常对每个节点执行常数次操作,因此时间复杂度为 ‌O(n)‌。

● 换根 DP 通常遵循一个固定的两遍 DFS 流程:
(一)第一次 DFS(固定根,收集信息)‌
(1)任选一个节点(通常为 1 号节点)作为初始根。
(2)进行一次自底向上的树形 DP,计算以该节点为根时,各子树的状态信息。这些信息通常包括:子树大小(siz[u])、子树内节点到根的距离和(dis[u])、或其他与问题相关的子树最优解
(二)第二次 DFS(换根,推导全局)‌
(1)这是算法的关键。基于第一次 DFS 得到的信息,从初始根节点开始,进行第二次 DFS。
(2)在遍历过程中,当从父节点 u 走向子节点 v 时,利用已知的以 u 为根时的全局答案 dp[u],推导出以 v 为根时的全局答案 dp[v]。
(3)这个推导过程就是 ‌“换根公式”‌,它分析了当根从 u 移到 v 时,哪些节点的贡献发生了变化(例如,深度增加或减少),并据此更新答案。

● 经典示例:所有节点到其他节点距离之和
这是一个最经典的换根DP问题,清晰地展示了换根公式的推导过程。
(1)问题‌:给一棵树,求以每个节点为根时,所有节点到该根节点的深度之和。
(2)定义‌:siz[u]:以 u 为根的子树中的节点数、dis[u]:以 u 为根时,所有节点到 u 的深度之和。

P3478_1

(3)步骤‌:
第一次 DFS‌:以节点 1 为根,计算 siz[u] 和初始的 dis[1]。
换根公式推导‌:当根从 u 换到其子节点 v 时,所有在 v 子树中的节点,到新根 v 的距离比到旧根 u 的距离‌减少1‌。这部分贡献总共减少 siz[v]。所有不在 v 子树中的节点(共 n - siz[v] 个),到新根 v 的距离比到旧根 u 的距离‌增加1‌。这部分贡献总共增加 n - siz[v]。
因此,dis[v] = dis[u] - siz[v] + (n - siz[v]) = dis[u] + n - 2 * siz[v]
第二次 DFS‌:应用此公式,即可从 dp[1] 递推计算出所有节点的 dis[i]。

● 本题数据量达到了 10^6,所以在代码中加入如下语句(https://blog.csdn.net/hnjzsyjyj/article/details/143176072),避免 TLE。

ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);

当然,也可以使用“快读(https://blog.csdn.net/hnjzsyjyj/article/details/120131534)”函数,代码如下。

int read() { //fast read
    int x=0,f=1;
    char c=getchar();
    while(c<'0' || c>'9') { //!isdigit(c)
        if(c=='-') f=-1;
        c=getchar();
    }
    while(c>='0' && c<='9') { //isdigit(c)
        x=x*10+c-'0';
        c=getchar();
    }
    return x*f;
}

【算法代码:邻接表存图

#include <bits/stdc++.h>
using namespace std;

const int N=1e6+5;
typedef long long LL;
LL siz[N],dis[N],imx;
vector<int> g[N];
int n,id;

void dfs1(int u,int fa)  {
    siz[u]=1;
    for(int v:g[u]) {
        if(v==fa) continue;
        dfs1(v,u);
        siz[u]+=siz[v];
        dis[u]+=dis[v]+siz[v];
    }
    return;
}

void dfs2(int u,int fa) {
    for(int v:g[u]) {
        if(v==fa) continue;
        dis[v]=dis[u]-siz[v]+n-siz[v];
        if(dis[v]>imx) {
            imx=dis[v];
            id=v;
        } else if(dis[v]==imx) id=min(id,v);
        dfs2(v,u);
    }
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin>>n;
    for(int i=1; i<n; i++) {
        int u,v;
        cin>>u>>v;
        g[u].push_back(v);
        g[v].push_back(u);
    }

    dfs1(1,-1);
    imx=dis[1], id=1;
    dfs2(1,-1);
    cout<<id<<endl;

    return 0;
}

/*
in:
8
1 4
5 6
4 5
6 7
6 8
2 4
3 4

out:
7
*/






【参考文献】
https://blog.csdn.net/hnjzsyjyj/article/details/157134513
https://mp.weixin.qq.com/s/efEtbnK1O7NI06k6wtmSmg
https://www.cnblogs.com/LYFmobai/p/10219339.html



 

posted @ 2026-01-20 14:41  Triwa  阅读(0)  评论(0)    收藏  举报