#P2727. 第3题-最小权值之和
          
                        
                                    
                      
        
              - 
          
          
                      1000ms
            
          
                      Tried: 23
            Accepted: 8
            Difficulty: 7
            
          
          
          
                       所属公司 : 
                              阿里
                                
            
                        
              时间 :2025年3月22日-阿里淘天(算法岗)
                              
                      
          
 
- 
                        算法标签>DFS          
 
第3题-最小权值之和
题解
题面描述
题目给出一棵含有 n 个结点的树,根节点为 1。每个结点的权值定义为该结点到结点 1 的边数。现在允许操作:选择一个非 1 号结点,将以该结点为根的子树重新挂接到结点 1 下。要求求出经过一次该操作后整棵树权值之和的最小值。
思路
本题的思路是:先利用深度优先搜索求出每个结点到根结点的距离(即深度)和每个结点的子树大小,然后计算初始的权值和 S=∑d;对于每个非根结点 v,将以 v 为根的子树挂接到 1 号结点后,该子树所有结点的深度都会降低 d[v]−1,所以改善量为 subtree_size[v]×(d[v]−1),最后取所有非根结点中最大的改善量,从 S 中减去即可得到最小的权值和。
- 
原始权值和的计算:
S=u=1∑nd[u].
初始时,每个结点的权值等于其到根结点 1 的距离。可以通过深度优先搜索(DFS)计算出所有结点的深度,累加即可得到原始权值和 - 
操作对权值和的影响分析:
设选择了结点 v(v=1),原来 v 的深度为 d[v],且以 v 为根的子树中的结点数为 subtree_size[v]。- 操作后,v 直接挂接到 1 下,新深度变为 1;而 v 子树内任意结点 u 的深度变为 1+(d[u]−d[v]).
 - 因此,这部分结点权值减少了
(d[u]−(1+d[u]−d[v]))=d[v]−1. - 整个子树的权值和减少量为
improvement = subtree_size[v]×(d[v]−1). 
 - 
目标转换:
为了使整体权值和最小,我们需要最大化提升量 improvement。因此,最终答案为
ans = S - maxv=1{ subtree_size[v]*(d[v]−1) }. 
C++ 解法
#include <iostream>
#include <vector>
using namespace std;
typedef long long ll;
int n; // n 表示树中结点的总数
vector<vector<int>> tree; // 存储树的邻接表
vector<int> depth; // 存储每个结点的深度
vector<int> subtree_size; // 存储每个结点的子树大小
ll total_depth = 0; // S,所有结点深度之和
ll best = 0; // 记录最大提升量
// 深度优先搜索函数
void dfs(int u, int parent, int d) {
    depth[u] = d; // 记录结点 u 的深度
    total_depth += d; // 累加到总权值和
    subtree_size[u] = 1; // 初始化子树大小(包含自身)
    for(auto v: tree[u]){
        if(v == parent) continue; // 避免回到父结点
        dfs(v, u, d+1); // 对子结点递归
        subtree_size[u] += subtree_size[v]; // 累加子树大小
    }
    if(u != 1){ // 只考虑非根结点
        ll improvement = (ll)subtree_size[u] * (d - 1);
        if(improvement > best) best = improvement; // 更新最大提升量
    }
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cin >> n;
    tree.resize(n+1);
    depth.resize(n+1,0);
    subtree_size.resize(n+1,0);
    // 读取边信息,构造树
    for(int i=1; i<=n-1; i++){
        int u, v;
        cin >> u >> v;
        tree[u].push_back(v);
        tree[v].push_back(u);
    }
    dfs(1, 0, 0); // 从结点 1 开始深搜,初始深度为 0
    cout << total_depth - best << "\n";
    return 0;
}
Python 解法
def main():
    import sys
    sys.setrecursionlimit(300000)  # 增加递归深度限制
    n = int(sys.stdin.readline())
    tree = [[] for _ in range(n+1)]  # 构造邻接表
    for _ in range(n-1):
        u, v = map(int, sys.stdin.readline().split())
        tree[u].append(v)
        tree[v].append(u)
    
    depth = [0]*(n+1)         # 存储每个结点的深度
    subtree_size = [0]*(n+1)  # 存储每个结点的子树大小
    total_depth = 0         # 累加所有结点深度,表示 S
    best = 0                # 记录最大提升量
    # 深度优先搜索函数
    def dfs(u, parent, d):
        nonlocal total_depth, best
        depth[u] = d  # 记录结点 $u$ 的深度
        total_depth += d  # 累加到总权值和
        subtree_size[u] = 1  # 初始化子树大小
        for v in tree[u]:
            if v == parent:
                continue  # 避免回到父结点
            dfs(v, u, d+1)  # 对子结点递归
            subtree_size[u] += subtree_size[v]  # 累加子树大小
        if u != 1:  # 只考虑非根结点
            improvement = subtree_size[u] * (d - 1)  # 计算提升量
            best = max(best, improvement)  # 更新最大提升量
    dfs(1, 0, 0)  # 从结点 1 开始深搜,初始深度为 0
    print(total_depth - best)  # 输出结果
if __name__ == '__main__':
    main()
Java 解法
import java.io.*;
import java.util.*;
public class Main {
    static int n; // n 表示树中结点的总数
    static ArrayList<ArrayList<Integer>> tree; // 存储树的邻接表
    static int[] depth; // 存储每个结点的深度
    static int[] subtreeSize; // 存储每个结点的子树大小
    static long totalDepth = 0; // S,所有结点深度之和
    static long best = 0; // 记录最大提升量
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        n = Integer.parseInt(br.readLine().trim());
        tree = new ArrayList<>();
        for(int i = 0; i <= n; i++){
            tree.add(new ArrayList<Integer>());
        }
        depth = new int[n+1];
        subtreeSize = new int[n+1];
        // 读取边信息,构造树
        for(int i = 1; i <= n-1; i++){
            String[] parts = br.readLine().split(" ");
            int u = Integer.parseInt(parts[0]);
            int v = Integer.parseInt(parts[1]);
            tree.get(u).add(v);
            tree.get(v).add(u);
        }
        dfs(1, 0, 0); // 从结点 1 开始深搜,初始深度为 0
        // 输出结果:totalDepth - best
        System.out.println(totalDepth - best);
    }
    
    // 深度优先搜索函数
    static void dfs(int u, int parent, int d) {
        depth[u] = d; // 记录结点 u 的深度
        totalDepth += d; // 累加到总权值和
        subtreeSize[u] = 1; // 初始化子树大小
        for(int v : tree.get(u)){
            if(v == parent) continue; // 避免回到父结点
            dfs(v, u, d+1); // 对子结点递归
            subtreeSize[u] += subtreeSize[v]; // 累加子树大小
        }
        if(u != 1){ // 只考虑非根结点
            long improvement = (long)subtreeSize[u] * (d - 1); // 计算提升量
            best = Math.max(best, improvement); // 更新最大提升量
        }
    }
}
        题目内容
小红拿到一棵树,结点总数为n,根节点为1
定义每个点的权值为到结点1的边数。
现在小红可以选择一个非1号结点,使得以该结点为根节点的子树成为1号结点的子结点。
求整棵树的最小权值之和是多少?
输入描述
第一行一个整数n(1≤n≤2×105),表示树的结点总数。
接下来n−1行,每行两个整数u,v(1≤u,v≤n),表示u和v之间有一条边。
输出描述
一个整数,表示整棵树的最小权值之和。
样例1
输入
5
1 2
2 3
3 4
4 5
输出
6