Day3

树的重心

#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
#include<cstring>
#include<queue>
#define m_p make_pair

using namespace std;
using ll = long long;
using u64 = unsigned long long;
using u32 = unsigned;
using u128 = unsigned __int128;
using i128 = __int128;
typedef pair<int,int> pii;
const int maxn = 1e5 + 10;

int n;
//vector<vector<int> > g;
vector<int> g[maxn];
int sz[maxn];//size of 
int min_balance = 0x3f3f3f3f,centroid = 0;
void dfs(int u,int fa){
    int max_branch = 0;
    sz[u] = 1;
    for(auto& x : g[u]) {//son
        if(x == fa) continue;
        dfs(x,u);
        sz[u] += sz[x];
        max_branch = max(max_branch,sz[x]);
    }
    max_branch = max(max_branch,n - sz[u]);
    if(max_branch < min_balance) {
        centroid = u;
        min_balance = max_branch;
    }
}
void solve()
{
	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);
	}
    dfs(1,0);
    cout << centroid << " " << min_balance << "\n";
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr); cout.tie(nullptr);
    int t = 1;
    while(t --) {
        solve();
    }
    //system("pause");
    return 0;
}



这是一个非常深刻的问题,触及到了递归(Recursion)树形DP最核心的逻辑:“自底向上(Bottom-up)的信息传递”

简单来说:因为爸爸不知道儿子有多大,必须等儿子量完自己的体重回来汇报,爸爸才能算出全家的总重量。

我们可以从以下三个角度来理解:

1. 数据的依赖关系 (Dependency)

我们看看 sz[u] 的定义公式:

\[sz[u] = 1 \text{ (自己)} + \sum sz[v] \text{ (所有孩子的子树大小)} \]

在这个公式里,sz[u] 的值依赖于 sz[v] 的值。

  • 在调用 dfs(v, u) 之前,v 只是一个没被访问过的节点,计算机根本不知道 v 下面挂了多少个节点,此时 sz[v] 可能还是初始值(比如 0)。
  • 只有当 dfs(v, u) 执行完毕(return回来) 之后,sz[v] 才存储了正确的大小。
  • 这时候,u 才能放心地执行 sz[u] += sz[v]

2. 形象的比喻:人口普查

想象你是一个村长 (u),你想统计全村有多少人。
你手下有几个组长 (v)

  • 错误的做法(先累加,再递归)
    你直接问组长:“你组里多少人?”
    组长还没去数呢,只能一脸懵逼地回答:“我还没去统计啊,现在的记录是 0。”
    你记下 0,然后组长才出门去数人。
    结果:你最后统计出来全村只有你自己 1 个人。

  • 正确的做法(先递归,再累加)
    你命令组长:“你先去(递归)把你那个组的人数数清楚,数完了回来告诉我。”
    组长出门了,挨家挨户数完了,回来告诉你:“村长,我那组有 100 人。”
    这时候(回溯),你把你本本上的数字加上 100。
    结果:统计正确。

3. 代码对比

我们可以看看如果反过来写会发生什么:

❌ 错误的写法

void dfs(int u, int fa) {
    sz[u] = 1;
    for (int v : adj[u]) {
        if (v == fa) continue;
        
        // 此时 dfs(v) 还没跑,sz[v] 还是 0 !
        sz[u] += sz[v]; // 加上了个寂寞
        
        // 现在才去算 sz[v],晚了!u 已经算完了
        dfs(v, u); 
    }
}

后果:整棵树算下来,所有节点的 sz 都是 1。

✅ 正确的写法 (回溯时更新)

void dfs(int u, int fa) {
    sz[u] = 1;
    for (int v : adj[u]) {
        if (v == fa) continue;
        
        // 1. 先派人下去把 v 及其子孙算清楚
        dfs(v, u); 
        
        // 2. 既然 dfs(v) 跑完了,sz[v] 现在肯定有值了
        // 把它加到我身上
        sz[u] += sz[v]; 
    }
}

总结

这就是“后序遍历(Post-order Traversal)”的思想:
先处理子节点,再处理根节点。
在树形 DP 中,凡是涉及“子树信息汇总”的操作(比如求子树大小、子树深度、子树权值和),都必须把计算逻辑写在递归调用之后

posted @ 2026-01-24 14:53  EcSilvia  阅读(2)  评论(0)    收藏  举报