惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

V
Visual Studio Blog
T
Tailwind CSS Blog
Google DeepMind News
Google DeepMind News
D
DataBreaches.Net
P
Proofpoint News Feed
Simon Willison's Weblog
Simon Willison's Weblog
Microsoft Azure Blog
Microsoft Azure Blog
MongoDB | Blog
MongoDB | Blog
腾讯CDC
月光博客
月光博客
A
Arctic Wolf
T
Threatpost
Jina AI
Jina AI
博客园 - 聂微东
美团技术团队
V
V2EX
云风的 BLOG
云风的 BLOG
宝玉的分享
宝玉的分享
Recent Commits to openclaw:main
Recent Commits to openclaw:main
M
MIT News - Artificial intelligence
S
Secure Thoughts
Martin Fowler
Martin Fowler
Webroot Blog
Webroot Blog
V
Vulnerabilities – Threatpost
爱范儿
爱范儿
人人都是产品经理
人人都是产品经理
Help Net Security
Help Net Security
Google Online Security Blog
Google Online Security Blog
博客园 - Franky
The Last Watchdog
The Last Watchdog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
阮一峰的网络日志
阮一峰的网络日志
博客园 - 【当耐特】
S
Schneier on Security
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Know Your Adversary
Know Your Adversary
Latest news
Latest news
有赞技术团队
有赞技术团队
AWS News Blog
AWS News Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Y
Y Combinator Blog
G
Google Developers Blog
NISL@THU
NISL@THU
H
Heimdal Security Blog
L
LangChain Blog
T
Troy Hunt's Blog
I
InfoQ
U
Unit 42
C
Check Point Blog
Engineering at Meta
Engineering at Meta

Louis C Deng's Blog

CS231n Lecture Note: Generative Models CS231n Lecture Note: Self-Supervised Learning CS231n Lecture Note: Large Scale Distributed Training 自動微分 | DIY 實現自己的 PyTorch From RNNs to Transformers CS231n Lecture Note VII: Recurrent Neural Networks Uncovering Batch & Layer Normalization CS231n Lecture Note VI: CNN Architectures and Training CS231n Lecture Note V: Convolution Neural Networks Basics Demystifying Softmax Loss: A Step-by-Step Derivation for Linear Classifiers Backpropagation: A Vector Calculus Perspective CS231n Lecture Note IV: Neural Networks and Backpropagation CS231n Lecture Note III: Optimization CS231n Lecture Note II: Linear Classifiers CS231n Lecture Note I: Image Classification CSAPP Cache Lab II: Optimizing Matrix Transposition CSAPP Cache Lab I: Let's simulate a cache memory! CS188 Search Lecture Notes III CS188 Search Lecture Notes II How to Use TouchID for Sudo Commands on macOS CS188 Search Lecture Notes I RECAP2025: 留白 CSAPP Bomb Lab 解析 x64 暫存器速查表 CSAPP Data Lab 解析 矩陣的 Modified Gram Schmidt 方法 聊一聊位掩碼(Bit Mask) 整數溢位與未定義行為 快速排序 幾種劃分方法討論 等待 記夢(DeepSeek 輔助創作) 午夜飛行 橋樑 黎明 或 2012 RECAP2024: 水檻臥聽雨 太陽、潮落 RECAP2023: 泡沫 題解 P1622 釋放囚犯 題解 P5888 傳球遊戲 殘陽似火 再會 飢餓藝術家 卡夫卡 Python 中的 zip() 和 enumerate() 泡沫 “救救孩子……”——談魯迅和《狂人日記》 想念 淺灘 蟬 · 夏 微風 觀星 浮塵 復活 【摘錄 | 轉載】普魯斯特 《追憶似水年華》第一卷 《在斯萬家那邊》(一) Time - Pink Floyd - The Dark Side of the Moon 【轉載】靜夜思變調 高樓 幻夢 冰 RECAP2022: 流星雨 清夜 割點 Tarjan 演算法 P3147 USACO16OPEN 262144 P 題解 馬拉車演算法 夜雨 層霧 從愚人節玩笑到真的玩笑(bushi): 淺談 lsnotes I made my own Hexo theme 題解 紀念品分組 題解 導彈攔截 如何高效使用搜尋引擎 用 GitHub Actions 格式化 C/C++ 程式碼 四季的天空 洛谷 7 月月賽 Div.2 總結 題解 最近公共祖先 (LCA) 用簡單的物理方法證明牛頓萊布尼茨公式 簡評榮耀手環6 海上生明月,天涯共此時。 我為什麼重新拿出了 iPod Swift 中的 SharedPreferance —— UserDefaults 凝視那一輪明月 用 GitHub Actions 部署 Hexo 部落格 遲來的日誌 - WWDC 2020 獎學金 vcpkg - 方便的 C/C++ 庫管理器 vimrc 配置指南 NextCloud - DIY NAS 解決方案 sudo shutdown -r now sudo shutdown -r now
P3354 Riv 河流 題解
Louis C Deng · 2022-11-23 · via Louis C Deng's Blog

最近在練樹形 DP,正好看到 這一道 虛標的紫題,但本蒟蒻不會寫,想出來了便記錄一下

題面 & 思考

先看題面,一顆有根樹,選定 k 個節點作為 ”伐木場“,求運送木料最小費用。注意木料費用是 dis * wood

最開始想到簡單的樹形揹包,狀態轉移方程:

1
f[i][k] = min(f[j][s] + cost, f[i][k]);

但是注意到,如果某個後代節點如果是伐木場,cost 不需要計算,所以狀態中還需要儲存後代的伐木場情況。

但是後代中鋸木廠不止一個。考慮到樹有唯一的父親,且同一深度祖先唯一,可以記錄最近的伐木場祖先,作為狀態的一部分。

f[i][j][k] 即 i 節點 最近的伐木場祖先為 j,後代(不算自己)有 k 個是伐木場。

但是由於 f 自己也可能是伐木場,轉移方程不同,需要分類討論,於是改成:f[i][j][k][0/1] 其中 0 代表自己不是伐木場, 1 表示自己伐木場。

狀態轉移方程

因為我們需要列舉祖先,在 DFS 時需要記錄 Fa 陣列:

1
2
3
DFS 開始時:fa[++tot] = x;

結束時:tot--;

簡單記錄祖先 stack。

回溯後,對於當前節點 x 和 子節點 y 每個祖先 ff

1
2
3
4
先對每個 k 賦初值,對於每種伐木場個數 l:
f[x][ff][l][0] += f[y][ff][0][0]; --> 當前節點不是伐木場,子節點 y 最近伐木場為 l,對任意 f,賦最大值,即 子結點中沒有伐木場。

f[x][ff][l][1] += f[y][x][0][0]; --> 當前節點是伐木場,子節點 y 最近伐木場為 當前節點,對任意 f,賦最大值,即 子結點中沒有伐木場。

然後就是樹形揹包:

1
2
3
4
f[x][ff][l][0] = min(f[x][ff][l][0], f[x][ff][l-s][0] + f[y][ff][s][0]); --> 當前節點不是伐木場,對 y 節點分配 s 個伐木場個數
f[x][ff][l][1] = min(f[x][ff][l][0], f[x][ff][l-s][1] + f[y][x][s][0]); --> 當前節點是伐木場,對 y 節點分配 s 個伐木場個數

唯一的不同是 y 節點最近祖先為 x

注意在列舉 l 時,由於是01揹包,需要倒過來列舉,不然狀態計算會疊加。(很容易理解)

做完了?好像還差一個 cost!

考慮到 祖先節點 ff cost 的貢獻,因為是 當前節點 W 乘以 到 ff 的總距離!

需要計算總距離,維護 dep 陣列,即當前節點到根節點距離,即可!很容易理解。ffx 的距離就是 dep[x] - dep[ff]

對於當前節點 x 每一個祖先 ff

1
2
3
4
5
6
7
8
if(l>=1){ --> 因為 l 需要 -1 ,要分類討論。
f[x][ff][l][0] = min(f[x][ff][l][0] + W[x] * (dep[x]-dep[ff]), f[x][ff][l][1]);
合併 01,因為回溯之後,1 的狀態不再被使用,便於下一步計算。
-1 是因為上文 f[x][ff][l][1] = min(f[x][ff][l][0], f[x][ff][l-s][1] + f[y][x][s][0]); 時,s+l-s = l 但是當前節點也是伐木場,所以狀態更新是 l+1 的,要 -1 獲取正確結果
}else{
l = 0 時,當前節點不可能為伐木場,直接新增到 ff 增加的貢獻
f[x][ff][l][0] += W[x] * (dep[x]-dep[ff]);
}

這裡再來聊一聊合併。為了便於討論,上文揹包轉移的時候,我們沒有考慮 y 的 1 的情況,而是在每次 y 回溯時 合併 0 和 1。這有點類似於滾動陣列?回溯後 0 不再表示之前的意義,而是我們最初設計的狀態:i 節點 最近的伐木場祖先為 j,後代(或自己)有 k 個是伐木場!

程式碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#include <cmath>
#include <cstdio>
#include <iostream>
#include <cstring>

using namespace std;

struct Node {
int to, next, d;
} NDS[201];

int head[201], vis[201];
int cnt = 0;
long long W[201];
int tot = 0;

void add(int a, int b, int d) {
NDS[cnt].to = b;
NDS[cnt].next = head[a];
NDS[cnt].d = d;
head[a] = cnt++;
}

int fa[201];
long long f[201][201][52][2];
long long dep[201];
long long n, k;

void dp(int x) {
fa[++tot] = x;
vis[x] = 1;
for (int i = head[x]; i != -1; i = NDS[i].next) {
int y = NDS[i].to;
if (vis[y]) continue;
dep[y] = dep[x] + NDS[i].d;
dp(y);
for (int j = tot; j >= 1; j--) {
int ff = fa[j];
for (int l = k; l >= 0; l--) {
f[x][ff][l][0] += f[y][ff][0][0];
f[x][ff][l][1] += f[y][x][0][0];
for(int s = l; s>=0; s--){
f[x][ff][l][0] = min(f[x][ff][l][0], f[x][ff][l-s][0] + f[y][ff][s][0]);
f[x][ff][l][1] = min(f[x][ff][l][1], f[x][ff][l-s][1] + f[y][x][s][0]);
}
}
}
}
for (int j = 1; j <= tot; j++) {
int ff = fa[j];
for (int l = k; l >= 0; l--) {
if(l>=1){
f[x][ff][l][0] = min(f[x][ff][l][0] + W[x] * (dep[x]-dep[ff]), f[x][ff][l-1][1]);
}else{
f[x][ff][l][0] += W[x] * (dep[x]-dep[ff]);
}

}
}

tot--;
}

int main() {

memset(head, -1, sizeof(head));
cin >> n >> k;
for (int i = 1; i <= n; i++) {
int w, v, d;
cin >> w >> v >> d;
W[i] = w;
add(i, v, d);
add(v, i, d);
}
dp(0);
cout << f[0][0][k][0] << endl;
return 0;
}