1. 程式人生 > >51nod1307(暴力樹剖/二分&dfs/並查集)

51nod1307(暴力樹剖/二分&dfs/並查集)

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>
 2
#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 }
View Code

解法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/並查集)