Hard

题目描述

给定一个以节点 0 为根的无向树,有 n 个节点,编号从 0 到 n - 1。每个节点 i 有一个整数值 vals[i],其父节点由 par[i] 给出。

如果一个节点子树内的节点子集满足:所选节点值的十进制表示中,数字 0 到 9 每个最多出现一次,则称该子集为"好"子集。

好子集的分数是其节点值的总和。

定义长度为 n 的数组 maxScore,其中 maxScore[u] 表示以节点 u 为根的子树中(包括 u 本身及其所有后代)好子集的最大可能分数。

返回 maxScore 中所有值的总和。

由于答案可能很大,请返回结果对 109 + 7 取模。

示例 1:

输入:vals = [2,3], par = [-1,0]
输出:8
解释:
- 以节点 0 为根的子树包括节点 {0, 1}。子集 {2, 3} 是好的,因为数字 2 和 3 只出现一次。此子集的分数是 2 + 3 = 5。
- 以节点 1 为根的子树只包括节点 {1}。子集 {3} 是好的。此子集的分数是 3。
- maxScore 数组是 [5, 3],所有值的总和是 5 + 3 = 8。

示例 2:

输入:vals = [1,5,2], par = [-1,0,0]
输出:15

示例 3:

输入:vals = [34,1,2], par = [-1,0,1]
输出:42

示例 4:

输入:vals = [3,22,5], par = [-1,0,1]
输出:18

约束:

  • 1 <= n == vals.length <= 500
  • 1 <= vals[i] <= 109
  • par.length == n
  • par[0] == -1
  • 0 <= par[i] < n for i in [1, n - 1]
  • 输入保证父节点数组 par 表示一个有效的树

解题思路

这是一个树形动态规划问题,核心在于用位掩码表示数字的使用状态。

主要思路:

  1. 状态表示:使用 10 位的二进制数表示数字 0-9 的使用情况,第 i 位为 1 表示数字 i 已被使用。

  2. 获取数字掩码:对于每个节点值,提取其包含的所有数字,转换为对应的位掩码。如果某个数字在值中出现多次,该节点就不能单独形成好子集。

  3. 树形DP:对于每个节点,维护一个映射 mask -> maxSum,表示在当前掩码状态下能获得的最大分数。

    • 从子节点收集所有可能的状态
    • 尝试将当前节点加入每个可能的状态中
    • 状态转移时检查掩码冲突
  4. 状态合并:当处理多个子节点时,需要合并不同子树的状态。两个状态可以合并当且仅当它们的掩码没有重叠位。

  5. 答案计算:对每个节点,找出其子树中所有可能状态的最大值,累加得到最终答案。

优化要点

  • 预处理每个节点值的数字掩码
  • 使用哈希表高效存储和查找状态
  • 剪枝:如果节点值本身包含重复数字,跳过该节点

代码实现

class Solution {
public:
    int goodSubtreeSum(vector<int>& vals, vector<int>& par) {
        const int MOD = 1e9 + 7;
        int n = vals.size();
        
        // 构建邻接表
        vector<vector<int>> children(n);
        for (int i = 1; i < n; i++) {
            children[par[i]].push_back(i);
        }
        
        // 获取数字的掩码
        auto getMask = [](int val) {
            int mask = 0;
            while (val > 0) {
                int digit = val % 10;
                if (mask & (1 << digit)) return -1; // 重复数字
                mask |= (1 << digit);
                val /= 10;
            }
            return mask;
        };
        
        vector<int> masks(n);
        for (int i = 0; i < n; i++) {
            masks[i] = getMask(vals[i]);
        }
        
        long long totalSum = 0;
        
        // 树形DP
        function<unordered_map<int, long long>(int)> dfs = [&](int u) -> unordered_map<int, long long> {
            unordered_map<int, long long> dp;
            dp[0] = 0; // 空集
            
            // 处理所有子节点
            for (int v : children[u]) {
                auto childDp = dfs(v);
                unordered_map<int, long long> newDp;
                
                // 合并当前状态和子节点状态
                for (auto& [mask1, sum1] : dp) {
                    for (auto& [mask2, sum2] : childDp) {
                        if ((mask1 & mask2) == 0) { // 没有重叠
                            int newMask = mask1 | mask2;
                            newDp[newMask] = max(newDp[newMask], sum1 + sum2);
                        }
                    }
                }
                dp = move(newDp);
            }
            
            // 尝试加入当前节点
            if (masks[u] != -1) {
                unordered_map<int, long long> newDp = dp;
                for (auto& [mask, sum] : dp) {
                    if ((mask & masks[u]) == 0) {
                        int newMask = mask | masks[u];
                        newDp[newMask] = max(newDp[newMask], sum + vals[u]);
                    }
                }
                dp = move(newDp);
            }
            
            // 计算当前子树的最大值
            long long maxVal = 0;
            for (auto& [mask, sum] : dp) {
                maxVal = max(maxVal, sum);
            }
            totalSum = (totalSum + maxVal) % MOD;
            
            return dp;
        };
        
        dfs(0);
        return totalSum;
    }
};
class Solution:
    def goodSubtreeSum(self, vals: List[int], par: List[int]) -> int:
        MOD = 10**9 + 7
        n = len(vals)
        
        # 构建邻接表
        children = [[] for _ in range(n)]
        for i in range(1, n):
            children[par[i]].append(i)
        
        # 获取数字的掩码
        def get_mask(val):
            mask = 0
            while val > 0:
                digit = val % 10
                if mask & (1 << digit):
                    return -1  # 重复数字
                mask |= (1 << digit)
                val //= 10
            return mask
        
        masks = [get_mask(val) for val in vals]
        total_sum = 0
        
        # 树形DP
        def dfs(u):
            nonlocal total_sum
            dp = {0: 0}  # 空集
            
            # 处理所有子节点
            for v in children[u]:
                child_dp = dfs(v)
                new_dp = {}
                
                # 合并当前状态和子节点状态
                for mask1, sum1 in dp.items():
                    for mask2, sum2 in child_dp.items():
                        if mask1 & mask2 == 0:  # 没有重叠
                            new_mask = mask1 | mask2
                            new_dp[new_mask] = max(new_dp.get(new_mask, 0), sum1 + sum2)
                
                dp = new_dp
            
            # 尝试加入当前节点
            if masks[u] != -1:
                new_dp = dp.copy()
                for mask, sum_val in dp.items():
                    if mask & masks[u] == 0:
                        new_mask = mask | masks[u]
                        new_dp[new_mask] = max(new_dp.get(new_mask, 0), sum_val + vals[u])
                dp = new_dp
            
            # 计算当前子树的最大值
            max_val = max(dp.values()) if dp else 0
            total_sum = (total_sum + max_val) % MOD
            
            return dp
        
        dfs(0)
        return total_sum
public class Solution {
    private const int MOD = 1000000007;
    
    public int GoodSubtreeSum(int[] vals, int[] par) {
        int n = vals.Length;
        
        // 构建邻接表
        List<int>[] children = new List<int>[n];
        for (int i = 0; i < n; i++) {
            children[i] = new List<int>();
        }
        for (int i = 1; i < n; i++) {
            children[par[i]].Add(i);
        }
        
        // 获取数字的掩码
        int GetMask(int val) {
            int mask = 0;
            while (val > 0) {
                int digit = val % 10;
                if ((mask & (1 << digit)) != 0) return -1; // 重复数字
                mask |= (1 << digit);
                val /= 10;
            }
            return mask;
        }
        
        int[] masks = new int[n];
        for (int i = 0; i < n; i++) {
            masks[i] = GetMask(vals[i]);
        }
        
        long totalSum = 0;
        
        // 树形DP
        Dictionary<int, long> DFS(int u) {
            var dp = new Dictionary<int, long> { [0] = 0 }; // 空集
            
            // 处理所有子节点
            foreach (int v in children[u]) {
                var childDp = DFS(v);
                var newDp = new Dictionary<int, long>();
                
                // 合并当前状态和子节点状态
                foreach (var kvp1 in dp) {
                    foreach (var kvp2 in childDp) {
                        int mask1 = kvp1.Key, mask2 = kvp2.Key;
                        long sum1 = kvp1.Value, sum2 = kvp2.Value;
                        
                        if ((mask1 & mask2) == 0) { // 没有重叠
                            int newMask = mask1 | mask2;
                            if (!newDp.ContainsKey(newMask)) newDp[newMask] = 0;
                            newDp[newMask] = Math.Max(newDp[newMask], sum1 + sum2);
                        }
                    }
                }
                dp = newDp;
            }
            
            // 尝试加入当前节点
            if (masks[u] != -1) {
                var newDp = new Dictionary<int, long>(dp);
                foreach (var kvp in dp) {
                    int mask = kvp.Key;
                    long sum = kvp.Value;
                    
                    if ((mask & masks[u]) == 0) {
                        int newMask = mask | masks[u];
                        if (!newDp.ContainsKey(newMask)) newDp[newMask] = 0;
                        newDp[newMask] = Math.Max(newDp[newMask], sum + vals[u]);
                    }
                }
                dp = newDp;
            }
            
            // 计算当前子树的最大值
            long maxVal = 0;
            foreach (long sum in dp.Values) {
                maxVal = Math.Max(maxVal, sum);
            }
            totalSum = (totalSum + maxVal) % MOD;
            
            return dp;
        }
        
        DFS(0);
        return (int)totalSum;
    }
}
var goodSubtreeSum = function(vals, par) {
    const MOD = 1000000007;
    const n = vals.length;
    
    // Build adjacency list
    const children = Array.from({length: n}, () => []);
    for (let i = 1; i < n; i++) {
        children[par[i]].push(i);
    }
    
    // Get digit mask for a value
    function getDigitMask(val) {
        let mask = 0;
        while (val > 0) {
            mask |= (1 << (val % 10));
            val = Math.floor(val / 10);
        }
        return mask;
    }
    
    // Check if two masks conflict (share common digits)
    function hasConflict(mask1, mask2) {
        return (mask1 & mask2) !== 0;
    }
    
    const maxScores = new Array(n);
    
    function dfs(node) {
        const nodeMask = getDigitMask(vals[node]);
        const nodeVal = vals[node];
        
        // Start with just this node
        let states = new Map();
        states.set(nodeMask, nodeVal);
        
        // Process each child
        for (const child of children[node]) {
            const childMaxScore = dfs(child);
            const childStates = childStates;
            
            const newStates = new Map(states);
            
            // Try to combine current states with child states
            for (const [mask1, sum1] of states) {
                for (const [mask2, sum2] of childStates) {
                    if (!hasConflict(mask1, mask2)) {
                        const newMask = mask1 | mask2;
                        const newSum = sum1 + sum2;
                        if (!newStates.has(newMask) || newStates.get(newMask) < newSum) {
                            newStates.set(newMask, newSum);
                        }
                    }
                }
            }
            
            states = newStates;
        }
        
        // Find maximum score for this subtree
        let maxScore = 0;
        for (const sum of states.values()) {
            maxScore = Math.max(maxScore, sum);
        }
        
        maxScores[node] = maxScore;
        window.childStates = states;
        return maxScore;
    }
    
    // Alternative approach using proper state passing
    function solve(node) {
        const nodeMask = getDigitMask(vals[node]);
        const nodeVal = vals[node];
        
        let allStates = new Map();
        allStates.set(nodeMask, nodeVal);
        
        for (const child of children[node]) {
            const childResult = solve(child);
            const childStates = childResult.states;
            
            const newStates = new Map(allStates);
            
            for (const [mask1, sum1] of allStates) {
                for (const [mask2, sum2] of childStates) {
                    if (!hasConflict(mask1, mask2)) {
                        const newMask = mask1 | mask2;
                        const newSum = sum1 + sum2;
                        if (!newStates.has(newMask) || newStates.get(newMask) < newSum) {
                            newStates.set(newMask, newSum);
                        }
                    }
                }
            }
            
            allStates = newStates;
        }
        
        let maxScore = 0;
        for (const sum of allStates.values()) {
            maxScore = Math.max(maxScore, sum);
        }
        
        maxScores[node] = maxScore;
        return { states: allStates, maxScore };
    }
    
    solve(0);
    
    let result = 0;
    for (const score of maxScores) {
        result = (result + score) % MOD;
    }
    
    return result;
};

复杂度分析

指标复杂度
时间-
空间-