樹形dp [JOI Open 2020] 發電站 / Power Plant
作為最強摸魚人的 BaiBaiShaFeng,這個題解也是發到洛谷上了,希望給過。
先輩們說的太簡略了我感覺有點難懂,雖然我的表達能力很弱,估計強不了多少。
注:參考過網上零散題解。
題意很好理解,我們就不過多敘述了。
不看炸掉的機子,我們實際上是在選擇一個聯通塊去覆蓋樹的一部分,而我們所要求的就是這個聯通塊的最大值。
我們統一在這個聯通快的最高點進行答案的統計,從子樹的貢獻向上遞歸,這個樣子問題可以從子樹中合并上來,進而考慮樹形 dp。
設 \(dp[u]\) 表示聯通塊的最高點還在祖先處時 \(u\) 子樹中的答案,形象的說,就是上不封頂。
這個時候我們只要選擇了 \(u\) 子樹中的發電機 \(u\) 自然會炸,因為最高點在祖先,上邊會有其他啟動的發電機。
所以我們一共有兩種決策:選擇所有子樹中的最佳情況或者干脆不選子樹,去選 \(u\) 上的發電機。
注意如果沒有發電機便只有第一種情況。
明顯不存在其他情況。
轉移于是乎就明顯起來了,我們在 \(\sum_{v\in son[u]} dp[v]-1\) 和 \(1\) 之間取最大就是 \(dp[u]\) 了。
這個式子對應了我們上邊說的兩種情況,下邊說說答案的統計。
我們規定了在最高點統計答案,每個點都可以是最高點,對于一個最高點,若不想讓這個點炸只能選擇一個子樹中的答案,若允許它炸就直接選擇所有子樹中的答案再讓它炸,第二種和 \(dp[u]\) 最終的結果應該是相等的,但是含義并不一樣,后邊為了方便寫在一起了。
代碼如下,這個題想起來有些麻煩但實現起來就很輕松。
#include <bits/stdc++.h>
using namespace std;
const int MN=1e6+116;
struct Node{
int nxt, to;
}node[MN];
int head[MN], tottt;
void insert(int u, int v){
node[++tottt].to=v;
node[tottt].nxt=head[u];
head[u]=tottt; return;
}
//以上是平平無奇的存圖
int n, dp[MN], has[MN], ans=0;//has表示介個點有沒有發電機
void Read(){
memset(dp,0,sizeof(dp));
memset(has,false,sizeof(dp));
cin>>n;
for(int i=1,u,v; i<n; ++i){
cin>>u>>v; insert(u,v); insert(v,u);
}
string s; cin>>s; s=' '+s;
for(int i=1; i<=n; ++i){
if(s[i]=='1') has[i]=true;
}
}
void dfs(int u, int father){
for(int i=head[u];i;i=node[i].nxt){
int v=node[i].to;
if(v==father) continue;
dfs(v,u); dp[u]+=dp[v];
ans=max(ans,dp[v]+(has[u]));//最高點不炸,選擇一棵子樹
}
if(has[u]) dp[u]=max(dp[u]-1,1);
ans=max(ans,dp[u]);//最高點可炸
}
int main(){
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
Read(); dfs(1,1); cout<<ans<<'\n';
return 0;
}
若有不足請告訴我,謝謝啦。

浙公網安備 33010602011771號