51nod1307(暴力树剖/二分&dfs/并查集)

Posted ygeloutingyu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了51nod1307(暴力树剖/二分&dfs/并查集)相关的知识,希望对你有一定的参考价值。

题目链接: 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/并查集)的主要内容,如果未能解决你的问题,请参考以下文章

51nod 1199 Money out of Thin Air(线段树+树剖分)

51Nod-1416 两点 (暴力DFS)

[51nod] 1267 4个数和为0 暴力+二分

51nod1464(trie + dfs)

[CTSC2008]网络管理(整体二分+树剖+树状数组)

51nod1287(二分/线段树区间最值&单点更新)