樹(shù)上差分算法解析與邊操作優(yōu)化實(shí)踐
1. 項(xiàng)目概述樹(shù)上差分與邊差分算法解析這道題目來(lái)自AcWing在線編程平臺(tái)的4963題核心考察的是如何高效處理樹(shù)結(jié)構(gòu)上的邊操作問(wèn)題。題目要求我們?cè)诮o定的一棵樹(shù)上通過(guò)一系列操作后確定可以安全移除的邊。這類問(wèn)題在實(shí)際應(yīng)用中非常常見(jiàn)比如網(wǎng)絡(luò)路由優(yōu)化、社交網(wǎng)絡(luò)關(guān)系分析等領(lǐng)域都會(huì)遇到類似場(chǎng)景。1.1 問(wèn)題核心需求題目給出一個(gè)具有N個(gè)節(jié)點(diǎn)的樹(shù)結(jié)構(gòu)以及M個(gè)操作請(qǐng)求。每個(gè)操作指定兩個(gè)節(jié)點(diǎn)u和v表示需要在這兩個(gè)節(jié)點(diǎn)之間的唯一路徑上的所有邊都執(zhí)行某種操作通常是增加或減少某個(gè)值。最終我們需要找出那些被所有操作覆蓋的邊或者說(shuō)滿足特定條件的邊。這類問(wèn)題的難點(diǎn)在于樹(shù)結(jié)構(gòu)的特殊性導(dǎo)致直接暴力解法時(shí)間復(fù)雜度太高O(M*N)需要高效處理大量區(qū)間更新操作最終需要精確到邊的統(tǒng)計(jì)結(jié)果1.2 算法選型思路針對(duì)這類問(wèn)題我們通常會(huì)考慮以下幾種算法暴力DFS/BFS對(duì)每個(gè)操作都遍歷整條路徑時(shí)間復(fù)雜度不可接受樹(shù)鏈剖分雖然可以解決問(wèn)題但實(shí)現(xiàn)復(fù)雜且常數(shù)較大樹(shù)上差分最優(yōu)選擇可以將時(shí)間復(fù)雜度降到O(M N)樹(shù)上差分算法之所以成為最優(yōu)解是因?yàn)轭A(yù)處理階段只需要O(N)時(shí)間每個(gè)操作可以在O(1)時(shí)間內(nèi)完成最終通過(guò)一次DFS遍歷就能得到所有邊的最終狀態(tài)2. 核心算法原理詳解2.1 差分?jǐn)?shù)組基礎(chǔ)概念在講解樹(shù)上差分之前我們先回顧一下一維差分?jǐn)?shù)組的概念。差分是一種常用的區(qū)間更新技巧它允許我們?cè)贠(1)時(shí)間內(nèi)完成任意區(qū)間的增減操作。對(duì)于普通數(shù)組arr我們定義其差分?jǐn)?shù)組diff滿足diff[0] arr[0]diff[i] arr[i] - arr[i-1] (i 0)這樣如果我們想對(duì)arr的區(qū)間[l,r]增加val只需要diff[l] valdiff[r1] - val最后通過(guò)前綴和運(yùn)算即可還原出更新后的arr數(shù)組。2.2 樹(shù)上差分的擴(kuò)展應(yīng)用將差分思想擴(kuò)展到樹(shù)結(jié)構(gòu)上我們需要考慮樹(shù)的特殊性質(zhì)樹(shù)是連通無(wú)向無(wú)環(huán)圖任意兩點(diǎn)之間有且只有一條唯一路徑邊和節(jié)點(diǎn)可以分別作為操作對(duì)象在本題中我們需要處理的是邊差分區(qū)別于點(diǎn)差分。邊差分的關(guān)鍵在于將每條邊關(guān)聯(lián)到其下方的節(jié)點(diǎn)通過(guò)節(jié)點(diǎn)的差分值來(lái)反映邊的狀態(tài)具體來(lái)說(shuō)對(duì)于邊(u,v)其中u是v的父節(jié)點(diǎn)我們將這條邊的狀態(tài)記錄在v節(jié)點(diǎn)上。這樣整棵樹(shù)的邊就與除根節(jié)點(diǎn)外的所有節(jié)點(diǎn)建立了一一對(duì)應(yīng)關(guān)系。2.3 LCA最近公共祖先的作用在處理路徑操作時(shí)我們需要快速找到任意兩個(gè)節(jié)點(diǎn)的最近公共祖先。LCA算法可以幫助我們將路徑拆分為u→LCA和v→LCA兩部分在這兩部分上分別應(yīng)用差分操作常用的LCA算法有樸素算法O(n)查詢倍增法O(logn)查詢需要預(yù)處理Tarjan離線算法O(1)查詢但需要預(yù)處理在本題中我們通常選擇倍增法因?yàn)轭A(yù)處理時(shí)間O(nlogn)可以接受查詢速度快適合處理大量操作實(shí)現(xiàn)相對(duì)簡(jiǎn)單3. 完整算法實(shí)現(xiàn)步驟3.1 數(shù)據(jù)結(jié)構(gòu)預(yù)處理首先我們需要建立樹(shù)的基本數(shù)據(jù)結(jié)構(gòu)并進(jìn)行必要的預(yù)處理const int MAXN 1e5 10; const int LOGN 20; vectorint tree[MAXN]; // 鄰接表存儲(chǔ)樹(shù)結(jié)構(gòu) int depth[MAXN]; // 節(jié)點(diǎn)深度 int parent[MAXN][LOGN]; // 倍增表 int diff[MAXN]; // 差分?jǐn)?shù)組 int edge_id[MAXN]; // 記錄邊與節(jié)點(diǎn)的對(duì)應(yīng)關(guān)系3.2 DFS預(yù)處理實(shí)現(xiàn)我們需要進(jìn)行一次DFS遍歷來(lái)完成以下工作計(jì)算每個(gè)節(jié)點(diǎn)的深度構(gòu)建倍增表建立邊與節(jié)點(diǎn)的對(duì)應(yīng)關(guān)系void dfs(int u, int p) { parent[u][0] p; depth[u] depth[p] 1; // 構(gòu)建倍增表 for(int i 1; i LOGN; i) { parent[u][i] parent[parent[u][i-1]][i-1]; } // 遍歷子節(jié)點(diǎn) for(int v : tree[u]) { if(v ! p) { edge_id[v] /* 記錄邊(u,v)的id */; dfs(v, u); } } }3.3 LCA查詢實(shí)現(xiàn)基于預(yù)處理好的倍增表我們可以高效查詢?nèi)我鈨牲c(diǎn)的LCAint lca(int u, int v) { if(depth[u] depth[v]) swap(u, v); // 將u提升到與v同一深度 for(int i LOGN-1; i 0; i--) { if(depth[parent[u][i]] depth[v]) { u parent[u][i]; } } if(u v) return u; // 同時(shí)向上尋找 for(int i LOGN-1; i 0; i--) { if(parent[u][i] ! parent[v][i]) { u parent[u][i]; v parent[v][i]; } } return parent[u][0]; }3.4 樹(shù)上差分操作實(shí)現(xiàn)對(duì)于每個(gè)操作(u, v)我們這樣處理void apply_diff(int u, int v, int val) { int ancestor lca(u, v); diff[u] val; diff[v] val; diff[ancestor] - 2 * val; }這個(gè)操作的核心思想是將路徑拆分為u→ancestor和v→ancestor兩部分在u和v處增加val表示從這兩個(gè)節(jié)點(diǎn)到根節(jié)點(diǎn)的路徑都增加val在ancestor處減去2*val抵消掉重復(fù)計(jì)算的部分3.5 結(jié)果收集與邊統(tǒng)計(jì)最后我們通過(guò)一次DFS遍歷來(lái)收集結(jié)果int result[MAXN]; // 存儲(chǔ)每條邊的最終值 void collect_result(int u, int p) { for(int v : tree[u]) { if(v ! p) { collect_result(v, u); result[edge_id[v]] diff[v]; diff[u] diff[v]; // 向上傳遞差分值 } } }4. 算法優(yōu)化與注意事項(xiàng)4.1 時(shí)間復(fù)雜度分析讓我們分析一下算法的時(shí)間復(fù)雜度DFS預(yù)處理O(NlogN)主要來(lái)自倍增表構(gòu)建M次操作處理每次O(1)差分操作 O(logN)的LCA查詢 → O(MlogN)結(jié)果收集O(N)總時(shí)間復(fù)雜度為O((NM)logN)這在N和M達(dá)到1e5量級(jí)時(shí)是完全可行的。4.2 常見(jiàn)實(shí)現(xiàn)陷阱在實(shí)際編碼中有幾個(gè)容易出錯(cuò)的地方需要注意根節(jié)點(diǎn)的選擇理論上可以選擇任意節(jié)點(diǎn)作為根但通常選擇節(jié)點(diǎn)1作為根更方便需要確保DFS預(yù)處理時(shí)正確處理根節(jié)點(diǎn)的parent和depth邊的編號(hào)處理需要建立邊與節(jié)點(diǎn)的明確對(duì)應(yīng)關(guān)系可以使用map或額外數(shù)組來(lái)記錄特別注意無(wú)向邊的雙向處理差分值的傳遞在collect_result中需要先處理子節(jié)點(diǎn)再累加差分值順序錯(cuò)誤會(huì)導(dǎo)致結(jié)果不正確邊界條件處理當(dāng)u或v就是LCA時(shí)的特殊情況根節(jié)點(diǎn)的特殊處理4.3 調(diào)試技巧當(dāng)算法出現(xiàn)問(wèn)題時(shí)可以采用以下調(diào)試方法小數(shù)據(jù)測(cè)試構(gòu)造簡(jiǎn)單的樹(shù)結(jié)構(gòu)如鏈狀、星狀手動(dòng)計(jì)算預(yù)期結(jié)果與程序輸出對(duì)比差分值打印在每個(gè)操作后打印關(guān)鍵節(jié)點(diǎn)的差分值驗(yàn)證差分操作是否正確LCA驗(yàn)證隨機(jī)選擇節(jié)點(diǎn)對(duì)驗(yàn)證LCA計(jì)算是否正確可以先用樸素算法驗(yàn)證結(jié)果可視化將最終結(jié)果標(biāo)記在樹(shù)的邊上直觀檢查是否符合預(yù)期5. 完整代碼框架示例以下是整合了所有步驟的完整代碼框架#include iostream #include vector #include algorithm using namespace std; const int MAXN 1e5 10; const int LOGN 20; vectorint tree[MAXN]; int depth[MAXN], parent[MAXN][LOGN]; int diff[MAXN], edge_id[MAXN], result[MAXN]; void dfs(int u, int p) { parent[u][0] p; for(int i 1; i LOGN; i) { parent[u][i] parent[parent[u][i-1]][i-1]; } for(int v : tree[u]) { if(v ! p) { depth[v] depth[u] 1; edge_id[v] /* 設(shè)置邊id */; dfs(v, u); } } } int lca(int u, int v) { if(depth[u] depth[v]) swap(u, v); for(int i LOGN-1; i 0; i--) { if(depth[parent[u][i]] depth[v]) { u parent[u][i]; } } if(u v) return u; for(int i LOGN-1; i 0; i--) { if(parent[u][i] ! parent[v][i]) { u parent[u][i]; v parent[v][i]; } } return parent[u][0]; } void apply_diff(int u, int v, int val) { int a lca(u, v); diff[u] val; diff[v] val; diff[a] - 2 * val; } void collect_result(int u, int p) { for(int v : tree[u]) { if(v ! p) { collect_result(v, u); result[edge_id[v]] diff[v]; diff[u] diff[v]; } } } int main() { int N, M; cin N M; // 建樹(shù) for(int i 1; i N; i) { int u, v; cin u v; tree[u].push_back(v); tree[v].push_back(u); } // 預(yù)處理 depth[1] 1; dfs(1, 0); // 處理操作 while(M--) { int u, v; cin u v; apply_diff(u, v, 1); } // 收集結(jié)果 collect_result(1, 0); // 輸出滿足條件的邊 for(int i 1; i N; i) { if(result[i] M) { // 根據(jù)題目條件調(diào)整 cout i ; } } return 0; }6. 算法擴(kuò)展與應(yīng)用樹(shù)上差分算法不僅適用于這道題目還可以解決許多類似的樹(shù)結(jié)構(gòu)問(wèn)題點(diǎn)差分當(dāng)操作對(duì)象是節(jié)點(diǎn)而非邊時(shí)差分公式變?yōu)閐iff[u] val, diff[v] valdiff[lca] - val, diff[parent[lca]] - val帶權(quán)操作每個(gè)操作可以有不同的權(quán)值只需將固定的1改為變量即可多條件查詢不只是統(tǒng)計(jì)覆蓋次數(shù)可以統(tǒng)計(jì)總和、最大值、最小值等動(dòng)態(tài)樹(shù)結(jié)構(gòu)結(jié)合LCT等數(shù)據(jù)結(jié)構(gòu)可以處理動(dòng)態(tài)變化的樹(shù)結(jié)構(gòu)在實(shí)際工程應(yīng)用中這種算法思想可以用于網(wǎng)絡(luò)流量監(jiān)控社交網(wǎng)絡(luò)影響分析分布式系統(tǒng)狀態(tài)同步版本控制系統(tǒng)變更追蹤理解了這個(gè)核心算法后可以解決LeetCode、Codeforces等平臺(tái)上的許多樹(shù)結(jié)構(gòu)問(wèn)題如路徑求和問(wèn)題子樹(shù)統(tǒng)計(jì)問(wèn)題樹(shù)結(jié)構(gòu)區(qū)間更新問(wèn)題掌握樹(shù)上差分的關(guān)鍵在于理解差分思想如何從線性結(jié)構(gòu)擴(kuò)展到樹(shù)結(jié)構(gòu)以及如何利用LCA來(lái)分解路徑操作。通過(guò)這道題目的練習(xí)可以建立起處理復(fù)雜樹(shù)結(jié)構(gòu)問(wèn)題的通用思維框架。

相關(guān)新聞

從技術(shù)專家到CTO:跨越執(zhí)行、規(guī)劃與戰(zhàn)略三層能力圖譜

從技術(shù)專家到CTO:跨越執(zhí)行、規(guī)劃與戰(zhàn)略三層能力圖譜

1. 從“技術(shù)天才”到“戰(zhàn)略舵手”:一次硅谷華人高管的典型躍遷最近硅谷科技圈有個(gè)消息挺有意思,一家叫AppLovin的AI和移動(dòng)廣告巨頭,任命了一位80后的中科大校友做CTO。這事兒看著就是個(gè)普通的人事變動(dòng),但如果你在硅谷的科技公司里…

2026/8/1 3:19:43 閱讀更多
GPT與Claude雙模型智能融合:解決AI開(kāi)發(fā)中的模型選擇難題

GPT與Claude雙模型智能融合:解決AI開(kāi)發(fā)中的模型選擇難題

這次我們來(lái)看一個(gè)讓工程師們不再需要在大模型之間二選一的解決方案——GPT 5.6 Sol 和 Claude Fable 5 的直接融合技術(shù)。這個(gè)項(xiàng)目不是簡(jiǎn)單的模型切換,而是通過(guò)智能融合機(jī)制讓兩個(gè)頂級(jí)模型協(xié)同工作,解決單一模型在某些場(chǎng)景下的局限性。從技術(shù)角度看&#…

2026/8/1 3:19:43 閱讀更多
Opus 5大模型技術(shù)解析:性能瓶頸、成本優(yōu)化與工程實(shí)踐指南

Opus 5大模型技術(shù)解析:性能瓶頸、成本優(yōu)化與工程實(shí)踐指南

最近不少開(kāi)發(fā)者都在討論一個(gè)現(xiàn)象:期待已久的Opus 5模型發(fā)布后,實(shí)際體驗(yàn)卻與預(yù)期有差距。這不僅僅是"又一個(gè)AI模型不好用"的簡(jiǎn)單吐槽,背后反映的是大模型技術(shù)發(fā)展到一個(gè)新階段后,開(kāi)發(fā)者面臨的實(shí)際挑戰(zhàn)。 如果你正在考慮…

2026/8/1 10:50:36 閱讀更多
SecureCRT自動(dòng)化登錄:從密碼管理到SSH密鑰代理的三種實(shí)現(xiàn)方案

SecureCRT自動(dòng)化登錄:從密碼管理到SSH密鑰代理的三種實(shí)現(xiàn)方案

1. 從手動(dòng)輸入到自動(dòng)化:為什么我們需要“智能輸入密碼” 每次登錄遠(yuǎn)程服務(wù)器,都要在SecureCRT的密碼框里手動(dòng)敲一遍那串又長(zhǎng)又復(fù)雜的密碼,這場(chǎng)景對(duì)運(yùn)維和開(kāi)發(fā)來(lái)說(shuō)太熟悉了。一天幾十次連接,不僅效率低下,敲錯(cuò)一兩個(gè)字符…

2026/8/1 10:50:36 閱讀更多
如何快速獲取Zenodo科研數(shù)據(jù):Python下載器終極指南

如何快速獲取Zenodo科研數(shù)據(jù):Python下載器終極指南

如何快速獲取Zenodo科研數(shù)據(jù):Python下載器終極指南 【免費(fèi)下載鏈接】zenodo_get Zenodo_get - a downloader for Zenodo records 項(xiàng)目地址: https://gitcode.com/gh_mirrors/ze/zenodo_get 還在為從Zenodo平臺(tái)下載大型科研數(shù)據(jù)集而煩惱嗎?面對(duì)數(shù)十…

2026/8/1 10:50:36 閱讀更多
微服務(wù) Docker 容器化部署與 CI/CD 上線

微服務(wù) Docker 容器化部署與 CI/CD 上線

微服務(wù) Docker 容器化部署與 CI/CD 上線開(kāi)發(fā):“在我電腦上跑得好好兒的!” 運(yùn)維:“你那叫開(kāi)發(fā)環(huán)境,我這叫生產(chǎn)環(huán)境,兩者之間隔了100個(gè)環(huán)境變量、50個(gè)依賴版本和無(wú)數(shù)個(gè)’我以為是一樣的’?!?Docker 站出來(lái)說(shuō)&#xff…

2026/8/1 10:50:36 閱讀更多
制度匯編實(shí)戰(zhàn)指南:從零到一構(gòu)建企業(yè)規(guī)范化管理體系

制度匯編實(shí)戰(zhàn)指南:從零到一構(gòu)建企業(yè)規(guī)范化管理體系

1. 項(xiàng)目概述:為什么制度匯編是管理者的必修課最近和幾位在不同單位負(fù)責(zé)行政或人力工作的朋友聊天,發(fā)現(xiàn)大家普遍面臨一個(gè)共同的痛點(diǎn):單位的規(guī)章制度散落在各個(gè)部門的電腦、共享盤甚至紙質(zhì)檔案柜里。新員工入職,想了解考勤規(guī)定&…

2026/8/1 10:40:36 閱讀更多
AMAT 0100-02186 I/O 分配 PCB

AMAT 0100-02186 I/O 分配 PCB

AMAT 0100-02186 I/O分配PCB板是應(yīng)用材料(Applied Materials)公司生產(chǎn)的一款用于半導(dǎo)體設(shè)備的I/O信號(hào)分配電路板。該型號(hào)(0100-02186)的核心特點(diǎn)如下:專用于Endura等半導(dǎo)體工藝腔室。集成信號(hào)路由與分配功能。連接控制…

2026/8/1 0:09:33 閱讀更多
Nissei Corp FFMN-32L-10-T0 40AX 三相異步電動(dòng)機(jī)

Nissei Corp FFMN-32L-10-T0 40AX 三相異步電動(dòng)機(jī)

Nissei Corp FFMN-32L-10-T0 40AX 三相異步電動(dòng)機(jī)是日本日清(Nissei)品牌的一款工業(yè)用三相異步電機(jī),適用于自動(dòng)化設(shè)備及通用機(jī)械驅(qū)動(dòng)。該型號(hào)(FFMN-32L-10-T0 40AX)的核心特點(diǎn)如下:三相交流異步電動(dòng)機(jī)。額定…

2026/8/1 0:09:33 閱讀更多
AMAT 0100-02186 I/O 分配 PCB

AMAT 0100-02186 I/O 分配 PCB

AMAT 0100-02186 I/O分配PCB板是應(yīng)用材料(Applied Materials)公司生產(chǎn)的一款用于半導(dǎo)體設(shè)備的I/O信號(hào)分配電路板。該型號(hào)(0100-02186)的核心特點(diǎn)如下:專用于Endura等半導(dǎo)體工藝腔室。集成信號(hào)路由與分配功能。連接控制…

2026/8/1 0:09:33 閱讀更多
Nissei Corp FFMN-32L-10-T0 40AX 三相異步電動(dòng)機(jī)

Nissei Corp FFMN-32L-10-T0 40AX 三相異步電動(dòng)機(jī)

Nissei Corp FFMN-32L-10-T0 40AX 三相異步電動(dòng)機(jī)是日本日清(Nissei)品牌的一款工業(yè)用三相異步電機(jī),適用于自動(dòng)化設(shè)備及通用機(jī)械驅(qū)動(dòng)。該型號(hào)(FFMN-32L-10-T0 40AX)的核心特點(diǎn)如下:三相交流異步電動(dòng)機(jī)。額定…

2026/8/1 0:09:33 閱讀更多