2025-11-22:最大好子樹分數。用go語言,給定一棵以節點 0 為根的無向樹,節點編號為 0 到 n-1。每個節點 i 有一個整數值 vals[i],其父節點由數組 par 給出。

對任一節點 u,考慮以 u 為根的那棵包含 u 本身及其所有後代的子樹。在這棵子樹裏任選若干節點(可以不選),把它們的值組成一個集合。如果把這些被選數值按十進制展開後,所有數位中每個十進制數字 0 到 9 在所有數位上最多出現一次,則稱該選取是合法的。這樣的集合的得分定義為所選節點值之和。

令 maxScore[u] 表示在以 u 為根的子樹內可以取得的合法集合的最大得分。問題要求返回所有 maxScore[u] 的總和(對 1000000007 取模)。

(説明:允許選空集合,其得分為 0;“每個數字最多出現一次”是對所有被選數值的所有數位的全侷限制。)

1 <= n == vals.length <= 500。

1 <= vals[i] <= 1000000000。

par.length == n。

par[0] == -1。

對於 [1, n - 1] 中的每一個 i ,都有 0 <= par[i] < n 。

輸入生成保證父數組 par 表示一棵有效的樹。

輸入: vals = [3,22,5], par = [-1,0,1]。

輸出: 18。

解釋:

以節點 0 為根的子樹包括節點 {0, 1, 2}。子集 {3, 22, 5} 不是好子集,因為數字 2 出現兩次。子集 {3, 5} 是好子集,此子集的分數是 3 + 5 = 8。

以節點 1 為根的子樹包括節點 {1, 2}。子集 {22, 5} 不是好子集,因為數字 2 出現兩次。子集 {5} 是好子集,此子集的分數是 5。

以節點 2 為根的子樹包括 {2}。子集 {5} 是 好的。此子集的分數是 5。

maxScore 數組為 [8, 5, 5],並且 maxScore 中所有值的總和是 8 + 5 + 5 = 18。因此,答案是 18。

題目來自力扣3575。

🔍 步驟一:預處理與初始化

代碼首先進行一些預處理工作:

  • 定義常量,包括模數 mod 和進制基數 D=10(代表十進制)。
  • 根據父節點數組 par 構建樹的鄰接表 g,用於表示節點間的父子關係。例如,g[p] 存儲了節點 p 的所有直接子節點。
  • 定義核心的數據結構 pair,用於記錄一個“合法數字集合”的狀態。其中 mask 是一個位掩碼(bitmask),表示該集合中所有數字的十進制數位使用情況(例如,第 d 位為1表示數字 d 已經出現過);val 表示構成這個集合的所有節點值之和。

🌳 步驟二:深度優先搜索(DFS)

對樹進行深度優先遍歷(DFS),從根節點(節點0)開始。DFS函數 dfs(x) 返回兩個值:

  1. f map[int]int:一個映射。鍵是位掩碼 mask,值是在以節點 x 為根的子樹中,能夠形成該掩碼的所有合法節點集合的最大和(val)。這相當於記錄了在子樹 x 內,各種不同數位組合下能獲得的最大分數。
  2. single []pair:一個列表,存儲了所有僅包含單個節點的合法狀態(即只選子樹 x 中的某一個節點構成的合法集合)。這個列表主要用於後續的合併操作。

🔢 步驟三:處理當前節點

對於當前遍歷到的節點 x

  1. 計算節點值的數位掩碼:將節點值 vals[x] 的各個十進制數位提取出來。如果在提取過程中發現某個數字重複出現(通過檢查位掩碼的相應位是否已置1),則判定該節點值本身非法,其掩碼 mask 被設為0。否則,掩碼 mask 記錄所有出現過的數字。
  2. 初始化狀態:如果節點 x 的值本身是合法的(mask > 0),那麼就將這個單節點狀態 {mask, val} 加入到 f 映射和 single 列表中。

🔁 步驟四:合併子樹信息

這是算法的核心部分。節點 x 需要合併其所有子節點 y 傳遞上來的狀態信息 (fysingleY)。

  • 啓發式合併(小的合併到大的):為了提高效率,代碼採用了啓發式合併策略。在合併子節點 y 的狀態之前,會比較當前已合併的 single 列表和子節點 ysingleY 列表的長度。如果 singleY 更長,就交換兩者的內容,確保總是將較小的集合合併到較大的集合中。這能有效降低合併操作的總時間複雜度。
  • 狀態合併與更新:合併過程遍歷子節點 ysingleY 列表中的每一個狀態 p。對於每個狀態 p
  • 檢查其值 v 是否大於當前 f 映射中相同掩碼 msk 對應的值。如果不是,則跳過,因為我們要的是最大和。
  • 如果 v 更大,則創建一個新的狀態映射 nf(初始為 f 的副本),並更新 nf[msk] 的值為 v
  • 然後,將新狀態 p 的掩碼 mskf 映射中所有已有的掩碼 msk2 進行組合。如果 mskmsk2 沒有公共數位(即 msk & msk2 == 0),説明這兩個數字集合可以合併成一個新的、數位仍然不重複的更大集合。計算新集合的和 v + s2,並嘗試更新 nf[msk|msk2] 的值,記錄更大的和。
  • 完成所有組合檢查後,將 f 更新為 nf

💯 步驟五:計算當前子樹的最大分數並累加答案

在合併完所有子節點的狀態後,遍歷當前節點 x 的最終狀態映射 f,找到其中所有合法節點集合的最大和 mx。將這個最大值 mx 累加到全局答案 ans 中。這步對應計算題目要求的 maxScore[u]

⏱️ 步驟六:複雜度分析

  • 時間複雜度:最壞情況下,每個節點的 single 列表可能包含 O(2^10) = O(1024) 種不同的數位掩碼狀態(因為數字0-9最多10位)。在合併子樹時,需要組合這些狀態。由於採用了啓發式合併,合併操作的總時間複雜度可以優化到大約 O(n * logn * 2^(2*10))。考慮到 n <= 500,這個複雜度在可接受範圍內。主導項是狀態合併部分。
  • 額外空間複雜度:主要消耗在DFS遞歸調用棧和存儲每個節點的狀態映射 f 及列表 single 上。遞歸棧深度為樹高,最壞情況O(n)。每個節點存儲的狀態數量最多為 O(2^10)。因此,總的空間複雜度約為 O(n * 2^10)。

💎 總結

這個解決方案的核心思想是使用深度優先搜索結合動態規劃位掩碼技術,通過啓發式合併來高效地計算每個子樹中滿足特定數位不重複條件的最大節點值之和。

Go完整代碼如下:

package main

import (
	"fmt"
	"maps"
)

func goodSubtreeSum(vals, par []int) (ans int) {
	const mod = 1_000_000_007
	const D = 10
	n := len(par)
	g := make([][]int, n)
	for i := 1; i < n; i++ {
		p := par[i]
		g[p] = append(g[p], i)
	}

	type pair struct{ mask, val int }
	var dfs func(int) (map[int]int, []pair)
	dfs = func(x int) (f map[int]int, single []pair) {
		f = map[int]int{}

		// 計算 val 的數位集合 mask
		val := vals[x]
		mask := 0
		for v := val; v > 0; v /= D {
			d := v % D
			if mask>>d&1 > 0 {
				mask = 0
				break
			}
			mask |= 1 << d
		}

		if mask > 0 {
			f[mask] = val
			single = append(single, pair{mask, val})
		}

		for _, y := range g[x] {
			fy, singleY := dfs(y)

			// 啓發式合併
			if len(singleY) > len(single) {
				single, singleY = singleY, single
				f, fy = fy, f
			}

			single = append(single, singleY...)

			// 把子樹 y 中的 mask 和 val 一個一個地加到 f 中
			for _, p := range singleY {
				msk, v := p.mask, p.val
				if v <= f[msk] {
					continue
				}
				nf := maps.Clone(f)
				nf[msk] = v
				for msk2, s2 := range f {
					if msk&msk2 == 0 {
						nf[msk|msk2] = max(nf[msk|msk2], v+s2)
					}
				}
				f = nf
			}
		}

		mx := 0
		for _, s := range f {
			mx = max(mx, s)
		}
		ans += mx

		return
	}
	dfs(0)
	return ans % mod
}

func main() {
	vals := []int{3, 22, 5}
	par := []int{-1, 0, 1}
	result := goodSubtreeSum(vals, par)
	fmt.Println(result)
}

2025-11-22:最大好子樹分數。用go語言,給定一棵以節點 0 為根的無向樹,節點編號為 0 到 n-1。每個節點 i 有一個整數值 vals[i],其父節點由數組 par 給出。 對任一節點_數位

Python完整代碼如下:

# -*-coding:utf-8-*-

from typing import List, Dict, Tuple
import copy

def goodSubtreeSum(vals: List[int], par: List[int]) -> int:
    mod = 1_000_000_007
    D = 10
    n = len(par)
    
    # 構建樹結構
    g = [[] for _ in range(n)]
    for i in range(1, n):
        p = par[i]
        g[p].append(i)
    
    ans = 0
    
    def dfs(x: int) -> Tuple[Dict[int, int], List[Tuple[int, int]]]:
        nonlocal ans
        f = {}
        
        # 計算 val 的數位集合 mask
        val = vals[x]
        mask = 0
        v = val
        valid_mask = True
        
        while v > 0:
            d = v % D
            if mask >> d & 1:
                valid_mask = False
                break
            mask |= 1 << d
            v //= D
        
        single = []
        if valid_mask and mask > 0:
            f[mask] = val
            single.append((mask, val))
        
        for y in g[x]:
            fy, singleY = dfs(y)
            
            # 啓發式合併:確保 single 是較大的那個
            if len(singleY) > len(single):
                single, singleY = singleY, single
                f, fy = fy, f
            
            single.extend(singleY)
            
            # 把子樹 y 中的 mask 和 val 一個一個地加到 f 中
            for msk, v in singleY:
                if v <= f.get(msk, 0):
                    continue
                    
                nf = copy.deepcopy(f)
                nf[msk] = v
                
                for msk2, s2 in f.items():
                    if msk & msk2 == 0:
                        combined_mask = msk | msk2
                        nf[combined_mask] = max(nf.get(combined_mask, 0), v + s2)
                
                f = nf
        
        # 更新答案
        mx = 0
        for s in f.values():
            mx = max(mx, s)
        ans += mx
        
        return f, single
    
    dfs(0)
    return ans % mod

def main():
    vals = [3, 22, 5]
    par = [-1, 0, 1]
    result = goodSubtreeSum(vals, par)
    print(result)

if __name__ == "__main__":
    main()

2025-11-22:最大好子樹分數。用go語言,給定一棵以節點 0 為根的無向樹,節點編號為 0 到 n-1。每個節點 i 有一個整數值 vals[i],其父節點由數組 par 給出。 對任一節點_#算法_02

C++完整代碼如下:

#include <iostream>
#include <vector>
#include <unordered_map>
#include <algorithm>
#include <functional>

using namespace std;

int goodSubtreeSum(vector<int>& vals, vector<int>& par) {
    const int mod = 1000000007;
    const int D = 10;
    int n = par.size();

    // 構建樹結構
    vector<vector<int>> g(n);
    for (int i = 1; i < n; i++) {
        int p = par[i];
        g[p].push_back(i);
    }

    int ans = 0;

    // 使用pair表示mask和val
    using Pair = pair<int, int>;

    function<tuple<unordered_map<int, int>, vector<Pair>>(int)> dfs;
    dfs = [&](int x) -> tuple<unordered_map<int, int>, vector<Pair>> {
        unordered_map<int, int> f;
        vector<Pair> single;

        // 計算val的數位集合mask
        int val = vals[x];
        int mask = 0;
        int v = val;
        bool valid_mask = true;

        while (v > 0) {
            int d = v % D;
            if (mask >> d & 1) {
                valid_mask = false;
                break;
            }
            mask |= 1 << d;
            v /= D;
        }

        if (valid_mask && mask > 0) {
            f[mask] = val;
            single.emplace_back(mask, val);
        }

        for (int y : g[x]) {
            auto [fy, singleY] = dfs(y);

            // 啓發式合併:確保single是較大的那個
            if (singleY.size() > single.size()) {
                swap(single, singleY);
                swap(f, fy);
            }

            single.insert(single.end(), singleY.begin(), singleY.end());

            // 把子樹y中的mask和val一個一個地加到f中
            for (auto [msk, v_val] : singleY) {
                if (v_val <= f[msk]) {
                    continue;
                }

                unordered_map<int, int> nf = f;
                nf[msk] = v_val;

                for (auto [msk2, s2] : f) {
                    if ((msk & msk2) == 0) {
                        int combined_mask = msk | msk2;
                        nf[combined_mask] = max(nf[combined_mask], v_val + s2);
                    }
                }

                f = move(nf);
            }
        }

        // 更新答案
        int mx = 0;
        for (auto [_, s] : f) {
            mx = max(mx, s);
        }
        ans = (ans + mx) % mod;

        return {f, single};
    };

    dfs(0);
    return ans;
}

int main() {
    vector<int> vals = {3, 22, 5};
    vector<int> par = {-1, 0, 1};
    int result = goodSubtreeSum(vals, par);
    cout << result << endl;
    return 0;
}

2025-11-22:最大好子樹分數。用go語言,給定一棵以節點 0 為根的無向樹,節點編號為 0 到 n-1。每個節點 i 有一個整數值 vals[i],其父節點由數組 par 給出。 對任一節點_數位_03