樹形DP進階:結合dfn序的線性化樹問題求解技巧

樹形DP進階:結合dfn序的線性化樹問題求解技巧

    • 一、dfn序與樹的線性化
      • 1.1 dfn序的基本概念
      • 1.2 樹形DP結合dfn序的優勢
    • 二、核心應用:子樹區間的DP優化
      • 2.1 子樹權值和的快速查詢與更新
        • 問題描述
        • 結合dfn序的解法
        • 代碼實現(前綴和版本)
        • 優化說明
      • 2.2 樹形DP與線性DP的結合(子樹序列拼接)
        • 問題描述
        • 結合dfn序的解法
        • 代碼實現
        • 核心思路
    • 三、進階技巧:dfn序與樹形DP的狀態融合
      • 3.1 帶限制的子樹節點選擇(結合線段樹)
        • 問題描述
        • 解法思路
        • 關鍵代碼
      • 3.2 dfn序在多子樹合并中的應用
    • 四、dfn序的優化邊界
      • 4.1 適用場景
      • 4.2 局限性
      • 總結

樹形DP求解中,我們常遇到需要頻繁處理“子樹區間查詢”“多子樹合并”或“樹與線性結構交互”的問題,這類問題單純依靠遞歸遍歷往往難以高效實現,而dfn序(深度優先搜索序號) 作為將樹結構轉化為線性結構的橋梁,能顯著降低問題復雜度。

一、dfn序與樹的線性化

1.1 dfn序的基本概念

dfn序(Depth-First Numbering)是對樹進行深度優先搜索(DFS)時,記錄節點首次被訪問的時間戳。它將樹的層級結構轉化為線性數組,同時具有一個關鍵特性:任意子樹在dfn序中對應一個連續的區間

  • 具體來說,對節點u進行DFS時,記錄:
    • dfn[u]:節點u的入棧時間(首次訪問序號);
    • size[u]:以u為根的子樹包含的節點數;
    • u的子樹在dfn序中對應區間為[dfn[u], dfn[u] + size[u] - 1]

二叉樹示例:

    1/ \2   3/ \4   5

DFS訪問順序為1→2→2(回退)→3→4→4(回退)→5→5(回退)→3(回退)→1(回退),dfn序為:

  • dfn[1]=1size[1]=5 → 子樹區間[1,5]
  • dfn[2]=2size[2]=1 → 子樹區間[2,2]
  • dfn[3]=3size[3]=3 → 子樹區間[3,5]
  • dfn[4]=4size[4]=1 → 子樹區間[4,4]
  • dfn[5]=5size[5]=1 → 子樹區間[5,5]

1.2 樹形DP結合dfn序的優勢

傳統樹形DP通過遞歸處理子樹,在以下場景中存在局限:

  • 需要多次查詢子樹信息(如“統計子樹中某屬性的和”);
  • 合并多個子樹的線性結構(如“將子樹的序列拼接后進行DP”);
  • 結合線段樹、前綴和等線性數據結構優化查詢。

而dfn序的線性化特性可解決這些問題:

  1. 子樹區間化:將子樹轉化為連續區間,使子樹查詢等價于區間查詢;
  2. 線性結構復用:可直接使用前綴和、線段樹等處理線性數據的工具;
  3. 狀態轉移簡化:多子樹的合并可轉化為區間拼接,降低遞歸嵌套復雜度。

二、核心應用:子樹區間的DP優化

2.1 子樹權值和的快速查詢與更新

問題描述

給定一棵帶權樹,支持兩種操作:

  1. 更新某節點的權值;
  2. 查詢以u為根的子樹的權值和。

要求高效實現這兩種操作(傳統遞歸查詢每次需O(size[u]),效率低)。

結合dfn序的解法
  1. 線性化映射

    • 對樹進行DFS,計算每個節點的dfn[u]size[u],子樹u對應區間[L, R] = [dfn[u], dfn[u] + size[u] - 1]
    • 將節點權值按dfn序存入數組val[dfn[u]] = w[u]
  2. 前綴和/線段樹優化

    • 用前綴和數組pre維護val的區間和,pre[i] = val[1] + ... + val[i]
    • 子樹和查詢:sum(u) = pre[R] - pre[L-1]
    • 單點更新:修改val[dfn[u]]后同步更新前綴和(或用線段樹實現O(log n)更新)。
代碼實現(前綴和版本)
import java.util.*;public class SubtreeSumWithDFN {List<List<Integer>> adj;int[] w; // 節點權值int[] dfn, size; // dfn序和子樹大小long[] pre; // 前綴和數組int n, dfnCnt;public SubtreeSumWithDFN(int n, int[] w) {this.n = n;this.w = w;adj = new ArrayList<>();for (int i = 0; i < n; i++) {adj.add(new ArrayList<>());}dfn = new int[n];size = new int[n];dfnCnt = 0;}public void addEdge(int u, int v) {adj.get(u).add(v);adj.get(v).add(u);}// 計算dfn序和size(根節點設為0)private void dfs(int u, int parent) {dfn[u] = dfnCnt++;size[u] = 1;for (int v : adj.get(u)) {if (v == parent) continue;dfs(v, u);size[u] += size[v];}}// 初始化前綴和public void build() {dfs(0, -1);pre = new long[n + 1]; // pre[0] = 0, pre[1] = val[0], ...for (int u = 0; u < n; u++) {pre[dfn[u] + 1] = pre[dfn[u]] + w[u]; // dfn從0開始,前綴和從1開始}}// 查詢子樹u的權值和public long querySubtreeSum(int u) {int L = dfn[u];int R = dfn[u] + size[u] - 1;return pre[R + 1] - pre[L];}// 更新節點u的權值(delta為變化量)public void update(int u, int delta) {w[u] += delta;int pos = dfn[u];for (int i = pos + 1; i <= n; i++) { // 前綴和更新(低效,實際用線段樹)pre[i] += delta;}}public static void main(String[] args) {int n = 5;int[] w = {10, 20, 30, 40, 50}; // 節點0~4的權值SubtreeSumWithDFN tree = new SubtreeSumWithDFN(n, w);tree.addEdge(0, 1); // 節點0對應示例中的1tree.addEdge(0, 2);tree.addEdge(2, 3);tree.addEdge(2, 4);tree.build();System.out.println(tree.querySubtreeSum(2)); // 子樹2包含節點2,3,4 → 30+40+50=120tree.update(3, 10); // 節點3權值+10 → 50System.out.println(tree.querySubtreeSum(2)); // 30+50+50=130}
}
優化說明
  • 上述代碼的更新操作是O(n),實際應用中應改用線段樹樹狀數組,將更新和查詢復雜度均降至O(log n)
  • 核心思想是利用dfn序的區間特性,將“子樹查詢”轉化為“區間查詢”,徹底擺脫遞歸遍歷的低效。

2.2 樹形DP與線性DP的結合(子樹序列拼接)

問題描述

給定一棵二叉樹,每個節點有一個字符,求所有子樹對應的字符串中,最長回文子序列的長度。

  • 示例:節點3的子樹包含字符'a''b''a',對應字符串"aba",最長回文子序列長度為3。
結合dfn序的解法
  1. 子樹字符串的線性化

    • 對樹進行DFS,按dfn序記錄節點字符,得到數組chars,子樹u的字符串對應chars[L..R]L=dfn[u], R=dfn[u]+size[u]-1)。
  2. 區間DP預處理

    • 預處理線性數組chars的區間回文子序列長度lps[L][R](經典區間DP,lps[L][R]表示chars[L..R]的最長回文子序列長度)。
  3. 樹形DP映射

    • 對每個節點u,其最長回文子序列長度即為lps[L][R],直接通過dfn區間查詢獲取。
代碼實現
import java.util.*;public class SubtreePalindromeWithDFN {List<List<Integer>> adj;char[] chars; // 節點字符int[] dfn, size;int[][] lps; // 區間最長回文子序列int n, dfnCnt;public SubtreePalindromeWithDFN(int n, char[] chars) {this.n = n;this.chars = chars;adj = new ArrayList<>();for (int i = 0; i < n; i++) {adj.add(new ArrayList<>());}dfn = new int[n];size = new int[n];dfnCnt = 0;lps = new int[n][n];}public void addEdge(int u, int v) {adj.get(u).add(v);}// 計算dfn序和size(二叉樹,左子樹優先)private void dfs(int u) {dfn[u] = dfnCnt++;size[u] = 1;for (int v : adj.get(u)) { // 假設子節點按左右順序存儲dfs(v);size[u] += size[v];}}// 預處理區間最長回文子序列private void preprocessLPS() {// 區間DP:lps[L][R] = 最長回文子序列長度for (int i = n - 1; i >= 0; i--) {lps[i][i] = 1; // 單個字符for (int j = i + 1; j < n; j++) {if (chars[i] == chars[j]) {lps[i][j] = (j == i + 1) ? 2 : lps[i + 1][j - 1] + 2;} else {lps[i][j] = Math.max(lps[i + 1][j], lps[i][j - 1]);}}}}// 獲取子樹u的最長回文子序列長度public int getMaxPalindrome(int u) {int L = dfn[u];int R = dfn[u] + size[u] - 1;return lps[L][R];}public static void main(String[] args) {int n = 3;char[] chars = {'a', 'b', 'a'}; // 節點0: 'a', 節點1: 'b', 節點2: 'a'SubtreePalindromeWithDFN tree = new SubtreePalindromeWithDFN(n, chars);tree.addEdge(0, 1); // 0是根,1和2是子節點(模擬二叉樹0->1->2)tree.addEdge(1, 2);tree.dfs(0);tree.preprocessLPS();System.out.println(tree.getMaxPalindrome(0)); // 子樹0: "aba" → 3System.out.println(tree.getMaxPalindrome(1)); // 子樹1: "ba" → 1}
}
核心思路
  • 通過dfn序將“子樹字符串”轉化為“線性區間”,復用區間DP的結果,避免對每個子樹單獨計算回文序列(傳統方法復雜度O(n^3),優化后為O(n^2))。
  • 體現了“樹的線性化”與“線性DP預處理”結合的高效性,尤其適合子樹操作頻繁的場景。

三、進階技巧:dfn序與樹形DP的狀態融合

3.1 帶限制的子樹節點選擇(結合線段樹)

問題描述

給定一棵樹上每個節點有價值v[u]和顏色c[u],求一個子樹u,使得:

  • 子樹中所有節點的顏色均為k
  • 子樹的總價值最大。
解法思路
  1. dfn序與顏色過濾

    • 按dfn序遍歷樹,記錄每個顏色k對應的節點在dfn數組中的位置,形成colorPos[k] = [p1, p2, ...](升序排列)。
  2. 區間最大值查詢

    • 用線段樹維護dfn序的價值前綴和,對顏色k,在colorPos[k]中查找連續區間(對應子樹),計算其價值和并取最大值。
  3. 子樹驗證

    • 對顏色k的每個連續區間[L, R],檢查是否存在節點u使得dfn[u] = Ldfn[u] + size[u] - 1 = R(即該區間是合法子樹)。
關鍵代碼
// 檢查區間[L, R]是否為合法子樹
private boolean isSubtree(int L, int R) {int u = findNodeByDFN(L); // 根據dfn值找到節點u(需維護dfn到節點的映射)return (dfn[u] + size[u] - 1) == R;
}// 查找顏色k的最大子樹價值
public int maxSubtreeValue(int k) {List<Integer> positions = colorPos.get(k);int maxSum = 0;// 遍歷顏色k的所有節點位置,檢查連續區間是否為子樹for (int i = 0; i < positions.size(); i++) {int L = positions.get(i);for (int j = i; j < positions.size(); j++) {int R = positions.get(j);if (!isSubtree(L, R)) continue;int sum = segmentTree.query(L, R); // 線段樹查詢區間和maxSum = Math.max(maxSum, sum);}}return maxSum;
}

3.2 dfn序在多子樹合并中的應用

當需要合并多個子樹的DP狀態時(如“選擇k個不相交子樹,使總價值最大”),dfn序的線性特性可將問題轉化為“在數組中選擇k個不重疊區間,使區間和最大”,直接復用線性DP的解法:

  • 定義dp[i][j]表示“前i個dfn位置,選擇j個子樹的最大價值”;
  • 轉移時判斷當前位置是否為子樹起點,若是則可選擇包含該子樹(dp[i+size[u]-1][j+1] = max(dp[i+size[u]-1][j+1], dp[i-1][j] + sum(u)))。

四、dfn序的優化邊界

4.1 適用場景

  • 子樹區間查詢頻繁:如子樹和、子樹最值、子樹序列屬性(回文、遞增等);
  • 樹與線性結構交互:需結合前綴和、線段樹、滑動窗口等線性算法;
  • 多子樹合并問題:子樹的選擇、組合需滿足線性約束(如不重疊、連續等)。

4.2 局限性

  • 動態樹不適用:樹結構頻繁修改(如節點插入/刪除)會導致dfn序失效,需用動態樹數據結構(如Link-Cut Tree);
  • 非DFS友好的問題:依賴廣度優先遍歷(BFS)特性的問題(如層次相關),dfn序優勢不明顯;
  • 區間映射 overhead:對簡單子樹問題(如單次查詢),遞歸遍歷可能比dfn序預處理更高效。

總結

dfn序作為樹結構線性化的核心工具,為樹形DP提供了“降維打擊”的思路——將復雜的樹狀問題轉化為直觀的線性問題,從而復用成熟的線性數據結構與算法:

  1. 子樹區間化:通過dfn[u]size[u]將子樹映射為連續區間,簡化查詢;
  2. 線性工具復用:前綴和、線段樹等可直接應用于子樹問題,提升效率;
  3. 狀態融合:使樹形DP與線性DP的狀態轉移無縫銜接,解決多子樹合并等復雜場景。

掌握dfn序技巧的關鍵:

  • 熟練計算和應用dfnsize數組;
  • 敏感識別“子樹問題可轉化為區間問題”的場景;
  • 結合具體問題選擇合適的線性數據結構(前綴和、線段樹等)。

That’s all, thanks for reading~~
覺得有用就點個贊、收進收藏夾吧!關注我,獲取更多干貨~

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/91853.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/91853.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/91853.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

九、Maven入門學習記錄

Maven介紹Maven作用統一項目結構Maven安裝&#xff08;注意配置阿里云私服時url要跟換成最新的&#xff09;IDEA創建Meavn項目Maven坐標介紹IDEA導入Maven項目依賴配置依賴傳遞依賴傳遞-排除依賴依賴范圍生命周期生命周期-執行特定生命周期生命周期-總結

中標喜訊 | 安暢檢測再下一城!斬獲重慶供水調度測試項目

安暢檢測在第三方檢測領域持續深耕&#xff0c;再傳捷報&#xff01;公司于2025年7月30日正式收到中標通知&#xff0c;成功拿下重慶水資源產業股份有限公司 “重慶西部科學城多水廠分區分壓供水優化調度研究項目&#xff08;軟件測試標段&#xff09;”。 此次中標不僅是市場…

銀河麒麟V10一鍵安裝DM8的腳本及高階運維SQL分享

介質下載地址名稱網址銀河麒麟高級服務器操作系統V10&#xff08;SP3&#xff09;用戶手冊https://www.kylinos.cn/support/document/60.htmlDM8 安裝手冊https://eco.dameng.com/document/dm/zh-cn/pm/install-uninstall.htmlDM 數據庫安裝&#xff08;Linux安裝&#xff09;h…

cobalt strike(CS)與Metasploit(MSF)聯動

CS —> MSF首先cs上創建一個http的外部監聽器。此時在CS服務端查看監聽的ip&#xff0c;發現并沒有開啟&#xff0c;需要到成功移交會話后才會啟動。netstat -tunlp | grep 7000在MSF中使用handler模塊&#xff0c;配置監聽。注意&#xff1a;目標機器的地址是rhost&#xf…

C# 類型

原文&#xff1a;C# 類型_w3cschool C#類型 類型定義值的藍圖。有不同的操作與不同類型相關聯。 在下面的示例中&#xff0c;我們使用兩個類型為int的常量&#xff0c;值為2 和 3。 static void Main() {int x 2 * 3;Console.WriteLine (x); } int 是一個表示整數值的構建…

確保TDesign Vue Next中t-color-picker組件在彈出顏色拾取面板時保證該面板不抖動方法參考

使用TDesign Vue Next中的組件t-color-picker時&#xff0c;在顏色面板彈出后&#xff0c;如果修改里面的顏色&#xff0c;發現這個顏色拾取面板會隨著顏色的改變位置不斷抖動&#xff0c;該問題由顯示顏色的數值文本的長度變化引起&#xff0c;因此要覆蓋組件內部顏色值文本的…

bypass

代碼解析修改自身bypass&#xff1a;第一句話$s"Declaring file object\n";定義一個s&#xff0c;值為Declaring file object第二句話$d$_SERVER[DOCUMENT_ROOT].$_SERVER[DOCUMENT_URI]; 不知道$_SERVER是什么&#xff0c;那就打印出來看看。輸入echo <pre>;…

C語言:構造類型學習

內容提要 構造類型 枚舉類型typedef 綜合案例&#xff1a;斗地主 構造類型 枚舉類型 建議&#xff1a;如果定義不相干的常理&#xff0c;使用宏定義&#xff08;符號常量&#xff09;&#xff1b;如果需要定義一組相關聯的常量&#xff0c;如月份0~11&#xff0c;星期0~6&#…

Prometheus-3--Prometheus是怎么抓取Java應用,Redis中間件,服務器環境的指標的?

1、Prometheus抓取Java應用的指標 1、數據來源&#xff1a;Java應用自身暴露的指標 Java應用的指標數據來源于應用代碼中定義的指標對象&#xff08;如Counter、Gauge、Histogram等&#xff09;&#xff0c;通過Prometheus客戶端庫&#xff08;如io.prometheus:client_java&…

42.安卓逆向2-補環境-unidbg安裝和簡單使用

免責聲明&#xff1a;內容僅供學習參考&#xff0c;請合法利用知識&#xff0c;禁止進行違法犯罪活動&#xff01; 內容參考于&#xff1a;圖靈Python學院 工具下載&#xff1a; 鏈接&#xff1a;https://pan.baidu.com/s/1bb8NhJc9eTuLzQr39lF55Q?pwdzy89 提取碼&#xff1…

數據結構與算法:哈希函數的應用及一些工程算法

前言這篇里的東西可以說了解了解就行了。一、哈希函數均勻性展示原本讓deepseek轉了一下老師的java代碼&#xff0c;但發現復刻起來太麻煩了。又因為這個理解就好&#xff0c;競賽不會有&#xff0c;所以就直接貼老師的java代碼了……import java.security.MessageDigest; impo…

交叉編譯ARM環境

ARM交叉編譯 可以采用交叉編譯工具鏈&#xff1a; sudo apt-get install aarch64-linux-gnu-gcc sudo apt-get install aarch64-linux-gnu-g sudo apt-get install gcc-arm-linux-gnueabi sudo apt-get install g-arm-linux-gnueabi 上面兩個是64位&#xff0c;下面兩個是…

算法思想 之 拓撲排序問題

歡迎拜訪&#xff1a;霧里看山-CSDN博客 本篇主題&#xff1a;算法思想 之 拓撲排序問題 發布時間&#xff1a;2025.8.4 隸屬專欄&#xff1a;算法 目錄算法介紹核心原理適用場景實現步驟(Kahn 算法)例題課程表題目鏈接題目描述算法思路代碼實現課程表 II題目鏈接題目描述算法思…

機器學習 入門——決策樹分類

決策樹是一種直觀且強大的機器學習算法&#xff0c;適用于分類和回歸任務。本文將全面介紹決策樹分類的原理、實現、調優和實際應用。一、什么是決策樹分類1.概念決策樹分類是一種樹形結構的分類模型&#xff0c;它通過遞歸地將數據集分割成更小的子集來構建決策規則。就像我們…

虛擬機中查看和修改文件權限

在虛擬機中管理文件權限是系統管理的重要部分&#xff0c;無論是在Linux還是Windows虛擬機中。下面我將詳細介紹兩種主要系統的權限管理方法。Linux虛擬機中的文件權限管理查看文件權限使用ls命令&#xff1a;ls -l 文件名輸出示例&#xff1a;-rwxr-xr-- 1 user group 1024 Ju…

圖像處理拉普拉斯算子

AI對話記錄&#xff0c;還沒有來得及仔細驗證和推導&#xff0c;目前只是記錄 當然可以&#xff01;我們來一步步推導拉普拉斯算子在旋轉變換下保持不變的數學過程。這里以二維情況為例&#xff0c;最直觀也最常見。&#x1f9ee; 拉普拉斯算子旋轉不變性的推導&#xff08;二維…

React ahooks——副作用類hooks之useThrottleEffect

useThrottleEffect 是 ahooks 提供的節流版 useEffect&#xff0c;它在依賴項變化時執行副作用函數&#xff0c;但會限制執行頻率。一、基本語法useThrottleEffect(effect: React.EffectCallback,deps?: React.DependencyList,options?: Options )二、參數詳解2.1. effect (必…

【建模與仿真】融合畫像約束和潛在特征的深度推薦算法

導讀&#xff1a; 基于深度學習的推薦算法已成為推薦系統領域的研究趨勢。然而&#xff0c;大多數現有工作僅考慮單一的用戶與物品交互數據&#xff0c;限制了算法的預測性能。本文提出一種畫像約束的編碼方式&#xff0c;并融合隱因子模型中的潛在特征&#xff0c;豐富了推薦…

華為網路設備學習-26(BGP協議 二)路徑屬性

一、屬性分類二、屬性含義①公認必遵&#xff1a;所有BGP對等體 必須識別 且 在Update報文中攜帶1.Origin2.AS-Path3.Next hop②公認自決&#xff1a;所有BGP對等體 必須識別但可以不在Update報文中攜帶 1.Local-Preference2.ATOMIC_Aggregate③可選傳遞&#xff1a;所有BGP對…

從0搭建YOLO目標檢測系統:實戰項目+完整流程+界面開發(附源碼)

文章目錄一、前言二、專欄介紹三、已有系統介紹3.0 基于yolo通用目標檢測系統&#xff08;手把手教你修改成為自己的檢測系統&#xff09;3.1 基于yolov8柑橘檢測系統3.2 基于yolov8艦船檢測系統3.3 基于yolo11人臉檢測系統3.4 基于yolov8無人機影像光伏板缺陷檢測系統一、前言…