問題:給定 $n\in[1,2\cdot 10^5]$ 個頂點 (1 ~ n) 以及它們各自儲存一個介於 $[1,10^9]$ 的數值,再接下來從第 2 個開始到最後的頂點,都會有自己的父/母頂點以及連接兩者的邊權重、也介於 $[1,10^9]$,第一個頂點是為樹根所以不用給資訊,而且保證輸入的圖是一棵樹。現在定義頂點 u 可以「控制」頂點 v 的條件為 v 在 u 底下 (兩者不相等) 而且 u 走到 v 的邊權重和不超過 v 儲存的數值。請印出每個頂點各自可以控制的頂點數量。
解法:首先可以觀察到愈靠近奴隸 v 的祖先愈有權力可以控制 v,所以對於每個非根頂點的奴隸 v,我都可以在從它往上走到根頂點的這段路上進行 binary search,找到最高可以控制它的主人,此時可以獲得這區間所有合法的主人。一個直觀的作法是直接把數量更新進這些主人的計數器裡,只是如此一來對每個奴隸都要更新這麼多人的資訊,會達到 $\mathcal O(n^2)$ 的時間複雜度,因此這邊我們是採用 difference array 技巧,這個技巧真的好用,我在這個奴隸 v 的最靠近合法主人計數器設下 +1,意味著由下往上累加每個主人所擁有的奴隸數量時,從這個奴隸 v 開始都要考慮到它,之後在 v 的最遠離合法主人的「父/母頂點」計數器設下 -1,意味著這個奴隸 v 已經不再受到從現在開始往上走的途中所遇到的所有頂點的控制了。這邊在思考的時候建議把 +1 和 -1 想成是多鋪一條繩子,所以計數器可以看成是有多少條繩子疊在上面,這樣子情感上比較可以相信程式碼中 dfs2 的累加,換句話說 difference array 也能用在樹上!實作的時候只要注意一下邊界條件,例如根頂點因為不受控制所以不用參與搜尋,以及可能有頂點完全不受任何一個頂點控制,還有 binary search 要如何縮小解區間比較方便等等,就可以了。
#include<bits/stdc++.h>usingnamespacestd;intn,a[200001],w[200001],dp[200001];vector<int>adj[200001],indices;vector<longlong>sum;voiddfs1(intv){indices.push_back(v);sum.push_back((sum.size()==0?0:sum.back())+w[v]);/* use binary search to find the smallest index "hi"
* such that sum.back() - sum[hi] <= a[v]. Note that
* such index must exist because a[v] >= 1 and the LHS
* can be 0 if hi == sum.size()-1.
*/if(v>1){// the root vertex cannot be controlled.
intlo=0,hi=indices.size()-1;while(lo<hi){intm=(lo+hi)/2;if(sum.back()-sum[m]<=a[v])hi=m;elselo=m+1;}dp[indices[indices.size()-2]]+=1;if(hi>=1)dp[indices[hi-1]]-=1;}for(autou:adj[v])dfs1(u);indices.pop_back();sum.pop_back();}intdfs2(intv){for(autou:adj[v])dp[v]+=dfs2(u);returndp[v];}intmain(){ios_base::sync_with_stdio(false);cin.tie(nullptr);// IO 優化
cin>>n;for(inti=1;i<=n;i++)cin>>a[i];for(inti=2;i<=n;i++){cin>>w[i];// parent
adj[w[i]].push_back(i);// push child
cin>>w[i];}dfs1(1);dfs2(1);for(inti=1;i<=n;i++)cout<<dp[i]<<" ";return0;}