51nod1307(暴力樹剖/二分&dfs/並查集)
阿新 • • 發佈:2017-07-05
href 存在 stdio.h cto pda long void 最大 amp
題目鏈接: http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1307
題意: 中文題誒~
思路:
解法1:暴力樹剖
用一個數組 num[i] 維護編號為 i 的邊當前最大能承受的重量. 在加邊的過程中根據給出的父親節點將當前邊所在的鏈上所有邊的num都減去當前加的邊的重量, 註意當前邊也要減自重. 那麽當num首次出現負數時加的邊號即位答案;
事實上這個算法的時間復雜度是O(n^2)的, 不過本題並沒有出那種退化成單鏈的數據, 所以直接暴力也能水過;
代碼:
1 #include <iostream> 2View Code#include <stdio.h> 3 using namespace std; 4 5 const int MAXN = 5e4 + 10; 6 struct node{ 7 int c, w, pre; 8 }gel[MAXN]; 9 int num[MAXN];//num[i]為編號為i的繩子當前可以承受的最大重量 10 11 int main(void){ 12 int n, ans = -1; 13 scanf("%d", &n); 14 for(int i = 0; i < n; i++){ 15 scanf("%d%d%d", &gel[i].c, &gel[i].w, &gel[i].pre); 16 if(ans != -1) continue; 17 num[i] = gel[i].c; 18 int cnt = i; 19 while(cnt != -1){ 20 num[cnt] -= gel[i].w; 21 if(ans == -1 && num[cnt] <= -1) ans = i; 22 cnt = gel[cnt].pre;//指向cnt的父親節點 23 } 24 } 25 if(ans == -1) cout << n << endl; 26 else cout << ans << endl; 27 return 0; 28 }
解法2: 二分 + dfs
很顯然在加邊的過程中所有邊的承受重量都是單調不減的, 那麽可以考慮二分答案. 不過要註意判斷函數的寫法, 每一次判斷都需要判斷當前 mid條 邊組成的樹的所有邊, 而不能只判斷當前 mid 所在鏈上的邊, 顯然其他鏈上也可能存在不合法的邊. 判斷所有邊的話可以 dfs 一遍, 回溯時判斷即可.
代碼:
1 #include <iostream> 2 #include <stdio.h> 3 #include <vector> 4 #define ll long long 5 using namespace std; 6 7 const int MAXN = 5e4 + 10; 8 struct node{ 9 int c, w, pre; 10 }gel[MAXN]; 11 vector<int> sol[MAXN]; 12 bool flag; 13 14 ll dfs(int u, int x){ 15 ll sum = gel[u].w; 16 if(u > x) return 0;//mid邊後面的不要算上去 17 for(int i = 0; i < sol[u].size(); i++){ 18 sum += dfs(sol[u][i], x); 19 } 20 if(sum > gel[u].c && u) flag = false;//0是一個虛根,並沒有對應的邊 21 return sum; 22 } 23 24 int main(void){ 25 int n; 26 scanf("%d", &n); 27 for(int i = 1; i <= n; i++){ 28 scanf("%d%d%d", &gel[i].c, &gel[i].w, &gel[i].pre); 29 gel[i].pre++; 30 sol[gel[i].pre].push_back(i); 31 } 32 int l = 1, r = n, cnt = n; 33 while(l <= r){ 34 flag = true; 35 int mid = (l + r) >> 1; 36 dfs(0, mid); 37 if(flag) cnt = mid, l = mid + 1; 38 else r = mid - 1; 39 } 40 printf("%d\n", cnt); 41 }View Code
解法3: 並查集
記錄每個節點的父節點 然後按輸入順序的倒敘 遍歷每一個節點 計算以當前節點為根的子樹的重量 ( 因為按照題目的輸入順序來說 當前節點要麽沒有子節點 要麽子樹已經遍歷完 算入當前樹的重量) 當遍歷到某個節點時 當前節點與父節點的邊無法承載當前節點為根的子樹 便從輸入序列最晚輸入的節點開始刪除 直到與父節點的邊的能夠承載當前節點為根的子樹 又或者已經把遍歷過的點都刪除完了 這個過程中 用並查集維護某個節點 屬於那一個跟節點 並且不斷的壓縮路徑 每個條路徑被壓縮一次 均攤時間 就是邊的數量 所以 這種做法很穩定的 O(n) 上面這段話是直接從討論中復制過來的 代碼:1 #include <iostream> 2 #include <stdio.h> 3 #include <vector> 4 #define ll long long 5 using namespace std; 6 7 const int MAXN = 1e5 + 10; 8 struct node{ 9 ll c, w, p; 10 }gel[MAXN]; 11 12 ll ww[MAXN]; 13 vector<int> vt[MAXN]; 14 int pre[MAXN], sol; 15 16 int find(int x){ 17 return pre[x] == x ? x : pre[x] = find(pre[x]); 18 } 19 20 void update(int u){ 21 for(int i = 0; i < vt[u].size(); i++){ 22 gel[u].w += gel[vt[u][i]].w; 23 pre[vt[u][i]] = u; 24 } 25 while(gel[u].w > gel[u].c){//u即為當前根節點 26 gel[find(sol)].w -= ww[sol]; 27 sol--; 28 } 29 } 30 31 int main(void){ 32 int n; 33 scanf("%d", &n); 34 for(int i = 1; i <= n; i++){ 35 scanf("%lld%lld%lld", &gel[i].c, &gel[i].w, &gel[i].p); 36 gel[i].p++; 37 vt[gel[i].p].push_back(i); 38 ww[i] = gel[i].w;//後面會對gel操作,所以需要先記錄下gel的初始值來 39 pre[i] = i; 40 } 41 sol = n; 42 for(int i = n; i > 0; i--){ 43 update(i); 44 } 45 printf("%d\n", sol); 46 return 0; 47 }View Code
51nod1307(暴力樹剖/二分&dfs/並查集)