#P2779. 第3题-树的最大权值
          
                        
                                    
                      
        
              - 
          
          
                      1000ms
            
          
                      Tried: 89
            Accepted: 7
            Difficulty: 5
            
          
          
          
                       所属公司 : 
                              阿里
                                
            
                        
              时间 :2025年3月30日-阿里云(开发岗)
                              
                      
          
 
- 
                        算法标签>思维          
 
第3题-树的最大权值
题解
本题要求我们在给定的树上给每个结点分配字母,目标是使得“回文路径”的最大长度(树的权值)达到最大。
实际上我们可以证明:
- 任何树中,一个简单路径的最大长度不会超过树的直径(最长路径);
 - 而我们又可以任意安排字母,因此在树上是否能找到一个回文路径,取决于我们能否在直径上选取一个子集的字母,使得这些字母能够构成回文。
 
因此问题转化为:
在全局给定的字母个数下,能构成的最长回文串长度是多少?
对于任意一个字母集合,要构成回文串,其要求是:
- 如果字符串长度为偶数,则每个字母出现次数必须为偶数;
 - 如果字符串长度为奇数,则至多只有一个字母出现次数为奇数,其余字母出现次数为偶数。
 
因此给定每个字母的个数,我们可以计算出能构成的最长回文串长度:
- 对每个字母,取其偶数部分的和
 - 如果存在至少一个字母的个数为奇数,则可以再加 1
 
记这个值为 L_max。
同时,我们用 DFS 或 BFS 求出树的直径长度(注意这里直径长度按照“结点数”来算)。
最后答案就是
答案=min(树的直径长度,Lmax)
例如样例中,字母个数为:
a:1, c:2, d:1, z:1,其余为 0。
计算得到偶数部分总和为 2(只有 c 的 2 个可以直接使用),再加上 1(因为有 a、d、z出现奇数次)得到 3;
而链式树直径长度为 5,所以答案为 min(5,3)=3。
算法复杂度
- 计算 L_max 只需遍历 26 个字母,时间复杂度 O(1)
 - 求树的直径需要两次 BFS/DFS,时间复杂度为 O(n)
 - 总体复杂度为 O(n),满足 n ≤ 10^5 的要求
 
Python 代码
# -*- coding: utf-8 -*-
import sys
from collections import deque
def main():
    # 读入26个字母的个数
    counts = list(map(int, sys.stdin.readline().split()))
    n = int(sys.stdin.readline().strip())
    
    # 构造树的邻接表
    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)
    
    # 求树的直径:使用两次 BFS
    def bfs(start):
        visited = [False]*(n+1)
        dist = [0]*(n+1)
        q = deque([start])
        visited[start] = True
        while q:
            u = q.popleft()
            for v in tree[u]:
                if not visited[v]:
                    visited[v] = True
                    dist[v] = dist[u] + 1
                    q.append(v)
        # 找到最远的节点及其距离
        max_node = 1
        max_dist = 0
        for i in range(1, n+1):
            if dist[i] > max_dist:
                max_dist = dist[i]
                max_node = i
        return max_node, max_dist
    # 第一次BFS,任选一个结点(例如1)
    u, _ = bfs(1)
    # 第二次BFS,从u出发找到最远点,距离为树的直径(边数)
    v, diameter_edges = bfs(u)
    # 转换成结点数(直径路径上的结点数 = 边数 + 1)
    diameter_nodes = diameter_edges + 1
    # 计算能构成的最长回文串长度 L_max
    even_sum = 0
    has_odd = False
    for cnt in counts:
        even_sum += cnt - (cnt & 1)  # cnt 去掉奇数部分,即偶数部分
        if cnt % 2 == 1:
            has_odd = True
    L_max = even_sum + (1 if has_odd else 0)
    # 最终答案为两者的最小值
    ans = min(diameter_nodes, L_max)
    print(ans)
if __name__ == '__main__':
    main()
JAVA 代码
import java.io.*;
import java.util.*;
public class Main {
    static int n;
    static ArrayList<Integer>[] tree;
    
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        // 读入26个字母的个数
        String[] parts = br.readLine().split(" ");
        int[] counts = new int[26];
        for (int i = 0; i < 26; i++) {
            counts[i] = Integer.parseInt(parts[i]);
        }
        n = Integer.parseInt(br.readLine().trim());
        
        // 初始化树的邻接表
        tree = new ArrayList[n+1];
        for (int i = 1; i <= n; i++) {
            tree[i] = new ArrayList<>();
        }
        for (int i = 1; i <= n-1; i++) {
            String[] edge = br.readLine().split(" ");
            int u = Integer.parseInt(edge[0]);
            int v = Integer.parseInt(edge[1]);
            tree[u].add(v);
            tree[v].add(u);
        }
        
        // 计算树的直径:使用两次BFS
        int[] firstBFS = bfs(1);
        int u = firstBFS[0]; // 第一次BFS得到的最远点
        int[] secondBFS = bfs(u);
        int diameterEdges = secondBFS[1]; // 边数
        int diameterNodes = diameterEdges + 1; // 结点数
        
        // 计算能构成的最长回文串长度 L_max
        int evenSum = 0;
        boolean hasOdd = false;
        for (int cnt : counts) {
            evenSum += cnt - (cnt & 1); // 取偶数部分
            if (cnt % 2 == 1) {
                hasOdd = true;
            }
        }
        int L_max = evenSum + (hasOdd ? 1 : 0);
        
        int ans = Math.min(diameterNodes, L_max);
        System.out.println(ans);
    }
    
    // BFS返回一个数组:[最远结点, 距离]
    static int[] bfs(int start) {
        boolean[] visited = new boolean[n+1];
        int[] dist = new int[n+1];
        Queue<Integer> queue = new LinkedList<>();
        queue.add(start);
        visited[start] = true;
        while (!queue.isEmpty()) {
            int u = queue.poll();
            for (int v : tree[u]) {
                if (!visited[v]) {
                    visited[v] = true;
                    dist[v] = dist[u] + 1;
                    queue.add(v);
                }
            }
        }
        int maxNode = start, maxDist = 0;
        for (int i = 1; i <= n; i++) {
            if (dist[i] > maxDist) {
                maxDist = dist[i];
                maxNode = i;
            }
        }
        return new int[]{maxNode, maxDist};
    }
}
C++ 代码
#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>
using namespace std;
int n;
vector<vector<int>> tree;
// BFS函数,返回一对 {最远结点, 距离}
pair<int,int> bfs(int start) {
    vector<bool> visited(n+1, false);
    vector<int> dist(n+1, 0);
    queue<int> q;
    q.push(start);
    visited[start] = true;
    while(!q.empty()){
        int u = q.front();
        q.pop();
        for (int v : tree[u]) {
            if (!visited[v]){
                visited[v] = true;
                dist[v] = dist[u] + 1;
                q.push(v);
            }
        }
    }
    int maxNode = start, maxDist = 0;
    for (int i = 1; i <= n; i++){
        if(dist[i] > maxDist){
            maxDist = dist[i];
            maxNode = i;
        }
    }
    return {maxNode, maxDist};
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    int counts[26];
    for (int i = 0; i < 26; i++){
        cin >> counts[i];
    }
    cin >> n;
    tree.resize(n+1);
    for (int i = 1; i <= n-1; i++){
        int u, v;
        cin >> u >> v;
        tree[u].push_back(v);
        tree[v].push_back(u);
    }
    
    // 两次 BFS 求树的直径(以结点数计,直径 = 边数 + 1)
    pair<int,int> first = bfs(1);
    pair<int,int> second = bfs(first.first);
    int diameterEdges = second.second;
    int diameterNodes = diameterEdges + 1;
    
    // 计算能构成的最长回文串长度 L_max
    int evenSum = 0;
    bool hasOdd = false;
    for (int i = 0; i < 26; i++){
        evenSum += counts[i] - (counts[i] & 1);
        if(counts[i] % 2 == 1)
            hasOdd = true;
    }
    int L_max = evenSum + (hasOdd ? 1 : 0);
    
    int ans = min(diameterNodes, L_max);
    cout << ans << "\n";
    return 0;
}
        题目内容
小红定义一棵树的权值为:
若一条简单路径u→v满足su+...+sv,是一个回文串。在所有这样的路径中,路径的长度的最大值是是该树的权值。现在小红给定一棵结点总数为n的树和 'a','b','c',...,'z'每种字母的个数,保证所有个数之和恰好等于n。
你需要将每个字母填入一个树的结点,使得该树的权值最大,输出树的最大权值。
输入描述
第一行输入一个长度为26的用,表示每个字母的个数,保证总和为
第二行输入一个整数n(1≤n≤105),表示的结点总数。
接下来n−1行,每行输入两个整数
u,v(1≤u,v≤n,u=v),表示树的一条边。
输出描述
输出一个整数,表示树的最大权值。
样例1
输入
1 0 2 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0
5
1 2
2 3
3 4
4 5
输出
3