【6.24校內test】T2 不老夢

【題目背景】測試

於萬人中萬幸得以相逢,剎那間澈淨明通。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-

相關文章
相關標籤/搜索