前言
關於下文中 \(lim\) 較小的最小點覆蓋問題,我們通常會對每個節點設出若干狀態轉移,而下文所說的問題是此問題的通解,但複雜度為平方級別
題意
給定一棵無根有權樹,每個點建消防站都有一定代價 \(c\),每個點都有一個限制 \(lim\),表示離它最近的消防站的最大距離。求讓所有點安全的最小代價。
分析
我們首先考慮對於一個點最在乎的是什麼,發現其實是離他最近的且建設消防站的點。
既然這個狀態如此重要,而且似乎直接表示出來非常的困難。我們考慮直接將其加入狀態裡。
設定動態規劃狀態
設 \(dp_{u,i}\) 為 \(u\) 這個點離他最近的且建設消防站的點為 \(i\) 且 \(u\) 子樹內結點全部安全。
轉移
在樹形 \(dp\) 的轉移時,我們通常用父親與兒子的關係去轉移,我們在這裡也是一樣的。
-
若 \(u,v\) 兩點最近點不相同考慮直接轉移即可。
-
若 \(u,v\) 兩點最近點相同,發現會重複統計一次貢獻,減去即可。
其中第 1 中轉移開個 \(min\) 陣列最佳化即可。
程式碼
void dfs1(int u, int par) {
// Debug(u);
for (auto [v, w] : G[u]) {
if (v == par)
continue;
dis[v] = dis[u] + w;
dfs1(v, u);
}
}
void dfs(int u, int par) {
// Debug(u);
for (int i = 1; i <= n; i++) {
if (can[u][i])
dp[u][i] = c[i];
else
dp[u][i] = inf;
}
for (auto [v, w] : G[u]) {
if (v == par)
continue;
dfs(v, u);
for (int i = 1; i <= n; i++)
dp[u][i] += min(f[v], dp[v][i] - c[i]);
}
f[u] = *min_element(dp[u] + 1, dp[u] + n + 1);
}
void init(int n) {
for (int i = 1; i <= n; i++) {
G[i].clear();
for(int j=1;j<=n;j++)
can[i][j]=0;
}
}
void Main() {
n = rd;
init(n);
for (int i = 1; i <= n; i++)
c[i] = rd;
for (int i = 1; i <= n; i++)
d[i] = rd;
for (int i = 1; i < n; i++) {
int u = rd, v = rd, w = rd;
G[u].pb({v, w});
G[v].pb({u, w});
}
for (int i = 1; i <= n; i++) {
dis[i] = 0, dfs1(i, 0);
for (int j = 1; j <= n; j++)
if (dis[j] <= d[i])
can[i][j] = 1;
}
dfs(1, 0);
cout << f[1] << endl;
}