【題目背景】測試
於萬人中萬幸得以相逢,剎那間澈淨明通。spa
成爲我所向披靡的勇氣和惶恐,裂山海,墮蒼穹。指針
愛若執炬迎風,熾烈而哀慟,諸般滋味皆在其中。code
韶華宛轉吟誦,蒼涼的光榮,急景凋年深情難共。blog
——銀臨《不老夢》排序
【問題描述】get
扶蘇翻遍了歌單卻沒有找到一首歌能作這個題的題目背景,因而放上了扶蘇最喜歡 的一首《不老夢》。it
與 Day1 的第二題同樣,今天的第二題依然是一道樹論題。(討厭數論題沒有之一!!)io
咱們定義一棵 n 個節點的樹爲一個有 n 個節點和 n-1 條邊的無向連通圖。for循環
若是咱們定義 u 是一顆樹 T 的根,那麼任意一個節點 v 到根的路徑就是從 v 出發到 達點 u 的簡單路徑上所通過的點的點集。能夠證實這樣的簡單路徑有且僅有一條。
定義一個節點 x 是節點 y 的孩子,當且僅當 x 和 y 之間有邊相連且 x 不在 y 到根的 路徑中。若是 x 是 y 的孩子,那麼定義 y 是 x 的家長節點。
若是我是 _rqy 那種毒瘤神仙的話,可能會問你每一個節點的孩子數不超過 k 的 n 個節 點的帶標號無根樹一共有多少個,惋惜這個問題我也不會,因此我不會問你這麼毒瘤的 問題。
扶蘇從一顆 n 個節點的樹的 1 號節點出發,沿着樹上的邊行走。固然咱們約定 1 號 節點是這棵樹的根。他所行走的規定是:當扶蘇在點 u 時,扶蘇要麼在 u 的孩子中選擇 一個沒有到達過得點 v 並行走到 v,要麼選擇回到 u的家長節點。
如今給每一個節點一個權值 w,其中 i 號節點的權值爲 wi。扶蘇有一些石子,他想給 這棵樹上的某一個節點放上石子。咱們規定扶蘇能在節點 u 放上石子當且僅當知足以下 條件:
一、扶蘇當前在節點 u
二、對於 u 的全部孩子節點v,節點 v 被放上了 wv 顆石子。
可是,扶蘇在任意時刻均可以取回任意節點的石子。
如今,扶蘇想問問你對於每一個節點,若是他想在 i 號節點上放 wi 顆石子,那麼他一 開始須要準備多少石子。
【輸入格式】
輸入文件名爲 yin.in。
輸入文件中有且僅有一組數據,數據的第一行是一個整數 n 表明樹的節點個數。
第二行有 n-1 個整數,第 i 個整數 pi 表明 i+1 號節點的家長節點的編號。
第三行有 n 個整數,第 i 個整數表明 wi。
【輸出格式】
輸出文件名爲 yin.out。
輸出一行 n 個整數,第 i 個整數表明想在 i 號節點上放 wi 顆石子須要準備的 石子個數。
顯然給了測試點的數據,咱們就能夠逐個擊破:
【測試點1】這個顯然沒有任何的技術含量啦,直接輸出w1就能夠啦。5pts get√
【測試點2-5】爆搜,搜出一個放石子的順序,而後 O(n) 的 check 是否合法。時間複雜度 O(n!n)。指望得分 20 分。(可是我不會寫嚶嚶嚶(╥╯^╰╥))
【測試點6-7】注意到根據題目規定的走法,在進入一個節點之後,必須遍歷完它的整個子樹, 不然一旦離開這個節點,再也沒法進入這棵子樹,從而致使該節點的某個孩子沒能放 上石子,致使這個節點不能放上石子。同時又有每一個節點放上石子之後,它的子樹的 石子能夠所有取回。設在節點 u 放石子須要有 ansu 個石子,則放完 u 之後能夠取回 ansu-wu 個石子。
因而考慮影響問題答案的顯然是從 u 進入每一個孩子的順序,因爲最多有兩個孩 子,直接比較一下就能夠知道先進入哪一個孩子更優秀了。時間複雜度 O(n),指望得分 10 分。
【測試點8-10】延續上一組測試點的思路,因爲只有最多 5 個孩子,能夠直接爆搜選孩子的順 序,看看哪一個更優秀。時間複雜度 O(n*x!),其中 x=5。指望得分 15 分。
【測試點11-14】(正解前導論,劃重點)
【測試點15-20】能夠發現上面的結論一樣適用於樹高更高的狀況,因而在 dfs 回溯的時候對子節 點排序,便可算出該節點的答案,指望得分 30 分。
好了如下是充滿了ZAY風起的STD(不用指針會死zay)
#include <cstdio> #include <vector> #include <algorithm> const int maxn = 100010; int n; int MU[maxn], ans[maxn]; std::vector<int>son[maxn]; void dfs(const int u); bool cmp(const int &_a, const int &_b); int main() { freopen("yin.in", "r", stdin); freopen("yin.out", "w", stdout); scanf("%d", &n); for (int i = 2, x; i <= n; ++i) {//直 接 i 從 2 開 始 枚 舉,就 不 用 i+1 了 scanf("%d", &x); son[x].push_back(i);//動 態 數 組 嘛 qwq } for (int i = 1; i <= n; ++i) { scanf("%d", MU + i);//指 針 可 海 星 } dfs(1); for (int i = 1; i < n; ++i) { printf("%d ", ans[i]); } printf("%d\n", ans[n]); return 0; } void dfs(const int u) { for (auto v : son[u]) {//遍歷u結點的全部兒子,賦值給v dfs(v);//先遍歷全部兒子,最後再操做1 } std::sort(son[u].begin(), son[u].end(), cmp);//將u結點的全部兒子按照ansi-wi不升序排序 int _ret = 0; //此時u結點全部的e子的ans都已經計算出來了 for (auto v : son[u]) {//對於葉子結點(沒有e子),不進行這個for循環 //對於其它結點, if (_ret >= ans[v]) {//顯然因爲遍歷是有順序的,當咱們遍歷到某些結點時,其以前遍歷的 //結點的石子和(除去結點u的e子),(顯然除了u結點所對應的兒子結點v們,這些v的兒子上的石子均可以刪除啦) //若是這些多出來的石子>某個未被遍歷到的結點所需石子數,顯然直接放過去就行了√ _ret -= ans[v];//這樣剩餘的石子就減小啦 //而後至關於ans[v]的貢獻是0 } else { ans[u] += ans[v] - _ret;//計算ans[u]:+它的e子結點所須要的石子-剩餘石子 _ret = ans[v] - MU[v];//表示遍歷完u的e子v這個結點對應滴子樹後,能夠收回除了u的e子v之外全部結點上的石頭 //所以ret=ans[v]-MU[v] } } ans[u] += std::max(0, MU[u] - _ret); //對於葉節點,ans[u]=0; //對於其它結點 } inline bool cmp(const int &_a, const int &_b) { return (ans[_a] - MU[_a]) > (ans[_b] - MU[_b]);//按照ansi-wi不升序排序 }
end-