問題:給定 $n\in[1,2\cdot10^5]$ 個禮物以及它們各自介於 $[-10^9,10^9]$ 的受喜愛程度。接下來的 n-1 行說明禮物 u 和禮物 v 會被一條繩子牽連在一起 (注意沒有指明誰上誰下),最後保證這整棵聖誕樹會以禮物 1 為根結點。現在兩人要同時從這棵樹上挑選禮物,挑選的方式是只要選定一個禮物,那麼所有掛在它底下 (延伸到葉子節點) 的禮物也要通通一起選出來,兩人的禮物當然不可重複 (兩棵子樹不可有交點),否則會吵架,請問如何調配能使得所有入選禮物的受喜愛程度總和最大?最大值為何?
解法:
這題的解法很經典地會使用 DP,而且不會太過困難,其實只要 1-pass DFS 就可以直接把答案記錄在頂點中。而當初卡住我思路的點的是在於如何同時維護這棵子樹底下 (一棵有最大和的子樹值) 以及 (兩棵子樹的最大和) 這兩種資訊,事實上我們只要同時維護三個陣列 (sum: 單純子樹底下的禮物喜愛度總和)、(one: 子樹底下的一棵最大和子樹值)、(two: 子樹底下的兩棵子樹的最大和) 即可。第一個 sum 陣列很簡單,如果是葉子的話直接存入自己禮物值,不是的話就先把直屬小孩 (children) 的 sum 全部加起來,再和自己禮物作加總並存入自己的 sum 值;第二個 one 陣列稍微要想一下,我們可以分成選或不選自己這個禮物,如果不選的話就是從 children 的 one 裡面挑最大的那一個,如果要選的話就是要再檢查自己的 sum 會不會更大,有更大就選,否則就不能選;而第三個 two 陣列是最不直覺的,我可能是某一個 child 底下就已經選好兩棵樹了,也有可能這個 child 底下的一棵最大子樹和另外一個 child 底下的一棵最大子樹才能拼成此時的最大和,由此觀察,我們其實可以先找哪個 child 有最大的 two 陣列值,然後「如果至少有 2 個 children 的話」再和所有 children 裡面有最大 one 和次大 one 的總和去作比較,得到這個節點真正的 two 值。最後根節點的 two 值就是我們要的。
#include<bits/stdc++.h>usingnamespacestd;intn,u,v,a[200001];longlongsum[200001],one[200001],two[200001];vector<int>adj[200001];voiddfs(intu){longlongone_firstmax=LLONG_MIN,one_secondmax=LLONG_MIN;one[u]=LLONG_MIN;two[u]=LLONG_MIN;for(autov:adj[u]){// avoid going back to the parent node
adj[v].erase(std::remove(adj[v].begin(),adj[v].end(),u),adj[v].end());dfs(v);// child's sum[v], one[v], two[v] are all ready to use after this.
sum[u]+=sum[v];one[u]=max(one[u],one[v]);two[u]=max(two[u],two[v]);// How to find the largest and the second largest element in an array in one pass?
// https://www.techcrashcourse.com/2016/08/program-to-find-largest-and-second-largest-element-array.html
if(one[v]>=one_firstmax){one_secondmax=one_firstmax;one_firstmax=one[v];}elseif(one[v]>=one_secondmax)one_secondmax=one[v];}sum[u]+=a[u];one[u]=max(one[u],sum[u]);if(adj[u].size()>=2)// should be equivalent to "one_secondmax > LLONG_MIN;"
two[u]=max(two[u],one_firstmax+one_secondmax);}intmain(){ios_base::sync_with_stdio(false);cin.tie(nullptr);// IO 優化
cin>>n;for(inti=1;i<=n;i++)cin>>a[i];for(inti=1;i<n;i++){cin>>u>>v;adj[u].push_back(v);adj[v].push_back(u);}dfs(1);if(two[1]==LLONG_MIN)cout<<"Impossible";elsecout<<two[1];return0;}