题意:
给一颗n点树,有边权和点权。边看成堆着土的隧道,土的数量就是边权,如果土的数量不为0,那么这条边不可以走,挖走1个土消耗1块钱。点上的权值x代表走到这个点能得到x块钱。有一个需要抵达的终点。
起点为0。从起点出发,可以选择一个能走到的隧道并挖隧道里面的土。可以不用挖完。走到点上一定会获得点权的钱。如果当前钱的数量小于0就破产
问最少需要多少钱可以走到终点
问至少带多少钱可以让你在最坏的情况下依然可以到达终点。
x[i]表示点i的权值,y[i]表示i的父亲边的权值
n
<
=
1
e
5
n <= 1e5
n<=1e5
解题思路:
先看最坏情况:
用dp[i]表示走i的父亲边最多可以花掉多少钱
那么
d
p
[
i
]
=
m
a
x
(
y
[
i
]
−
1
,
y
[
i
]
−
x
[
i
]
+
∑
d
p
[
v
]
)
dp[i] = max(y[i]-1, y[i]-x[i]+\sum dp[v])
dp[i]=max(y[i]−1,y[i]−x[i]+∑dp[v])
其中v是i的儿子。把终点的点权设置为无穷大,那么dp[0]+1就是答案了。
再看最好情况花多少钱。
首先对于一个子树i,我们想知道它最少花多少钱能赚到钱。
- 如果x[i] > y[i], 进入i至少要花y[i],可以赚到x[i]-y[i]元,而它的子树部分都没有处理,但是这不重要。因为我们知道了进入i最少要花多少钱才能赚到钱,接下来要么花更多的钱赚更多的利润,要么利用当前已经赚到的钱赚更多的利润。
- 否则,当前进入i是亏钱的,我们递归处理i的每个儿子,这样就知道了每个儿子最少需要多少钱才能赚到钱。那我们肯定优先需要的钱少的儿子去赚钱。用数据结构《堆》来维护可以进入赚钱的儿子。直到当前从亏钱变成赚钱。此时i的堆里面还有一些儿子没有走。
- 在第2步中的细节:当我们在一个儿子v中赚完钱之后,把v从i的堆中删除,并且把v的堆(也就是v没赚完钱的儿子)和i的堆合并,因为i赚了v之后,v的还没赚钱的儿子都可以被i访问了。
最后节点0赚钱所需要的最小花费就是答案。
代码:
代码中堆的合并用可并堆实现,总体时间
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)。当然也可以尝试启发式合并
O
(
n
l
o
g
2
n
)
O(nlog^2n)
O(nlog2n)。
#include<bits/stdc++.h>
#define ll long long
#define pb push_back
#define fors(i, a, b) for(int i = (a); i < (b); ++i)
using namespace std;
const int maxn = 2e5 + 50;
int ch[maxn*20][2], cost[maxn*20], id[maxn*20], dis[maxn*20], tot = 0;
int new_node(int a, int b){
++tot;
cost[tot] = a;
id[tot] = b;
dis[tot] = ch[tot][0] = ch[tot][1] = 0;
return tot;
}
int merge(int x,int y)
{
if(!x) return y;
if(!y) return x;
if(cost[x] > cost[y]) swap(x,y);
ch[x][1]=merge(ch[x][1],y);
if(dis[ch[x][1]]>dis[ch[x][0]])
swap(ch[x][1],ch[x][0]);
dis[x]=dis[ch[x][1]]+1;
return x;
}
int n;
int x[maxn], y[maxn];
vector<int> g[maxn];
const int inf = 0x3f3f3f3f;
void init(){
scanf("%d", &n);
fors(i,1,n){
int p; scanf("%d%d%d", &p, &x[i], &y[i]);
if(x[i] == -1) x[i] = inf;
g[p].pb(i);
}
}
int bad[maxn];
void dfs_bad(int u){
bad[u] = y[u]-1;
int sum = y[u] - x[u];
for(int v: g[u]){
dfs_bad(v);
sum += bad[v];
}
bad[u] = max(sum, bad[u]);
return;
}
vector<int> res[maxn];
int c[maxn], p[maxn];
#define P pair<int, int>
// cost id
const int lim = 5e8;
int cnt = 0;
int root[maxn];
void dfs(int u){
c[u] = y[u];
p[u] = x[u] - y[u];
root[u] = 0;
for(int v: g[u]){
dfs(v);
if(p[v] > 0) root[u] = merge(root[u], new_node(c[v], v) );
}
while(p[u] <= 0 && root[u]){
int v = id[root[u]];
root[u] = merge(ch[root[u]][0], ch[root[u]][1]);
if(c[u] + p[u] >= c[v]){
p[u] += p[v];
}else{
c[u] += c[v]-(p[u] + c[u]);
p[u] += p[v];
}
root[u] = merge(root[u], root[v]);
}
return;
}
void sol(){
dfs_bad(0);//bad[0]+1 is worst ans
dfs(0);
cout<<bad[0]+1<<" "<<c[0]<<endl;
}
int main()
{
init();
sol();
}