题解:P10759 [BalticOI 2024] Jobs
_saltFish_ · · 题解
看到这一题我就想到了另一个题目,对比之下发现,这不就是 P7925 的加强版吗。怪不得那么眼熟。
思路
回归正题,我们考虑如何解决本题。题面很明显给出了一个森林,但并不妨碍,我们建出一个虚根
首先,根据一个正常人的思维,我们只会在保证能够赚到钱的时候才会进入某个子树内,否则进去只会亏本,还不如不进去。
有了这个前提,我们希望对于每个点求出一个值
假设我们已经求出了
上述过程为
接下来介绍本题的重点,如何求出
一个朴素的想法是,我们对于每一个点,采用与求答案类似的方法,从子树的根开始拓展整棵子树,并动态地维护我们的投入钱数
现在我们就切掉了 P7925。这个过程明显是
考虑优化,我们发现,在求
我们发现,在求
为了方便表述,我们将求完一个点的 只是我在画图的时候画成了一条弧线。
这样就好办了,我们在求完一个点的
至于可并堆的合并,我们可以采用启发式合并。
简单分析一下复杂度,边界弧只会向下推不会向上推,所以每个点只会被边界弧扫到一次,这个过程是
那么我们就愉快地做完了这一题。本人比较懒,所以从 xiezheyuan 巨佬那要到了 pbds 可并堆来用。
#include <iostream>
#include <bits/extc++.h>
using namespace std;
#define LL long long
#define heap __gnu_pbds::priority_queue<int, cmp, __gnu_pbds::pairing_heap_tag>
const int N = 3e5 + 5;
int n;
int h[N], to[N], nxt[N], tot;
LL f[N], got[N], p[N], s;
struct cmp {
bool operator () (int x, int y) {
return f[x] > f[y];
}
};
heap q[N];
void add(int u, int v) {
nxt[++tot] = h[u], to[h[u] = tot] = v;
}
void push(int u) {
LL now = 0;
f[u] = 0;
q[u].push(u);
while(!q[u].empty()) {
int cur = q[u].top();
q[u].pop();
if(f[cur] == 2e18) {
break;
}
if(now < f[cur]) {
f[u] += f[cur] - now;
now = f[cur];
}
if(cur == u) {
now += p[cur];
for(int i = h[cur]; i; i = nxt[i]) {
int v = to[i];
q[u].push(v);
}
} else {
now += got[cur];
if(q[u].size() < q[cur].size()) {
q[u].swap(q[cur]);
}
q[u].join(q[cur]);
}
if(now > f[u]) {
got[u] = now - f[u];
return ;
}
}
f[u] = 2e18;
}
void dfs(int u) {
for(int i = h[u]; i; i = nxt[i]) {
int v = to[i];
dfs(v);
}
push(u);
}
void work() {
LL now = s;
heap node;
node.push(1);
while(!node.empty()) {
int u = node.top();
node.pop();
if(now < f[u]) {
break;
}
now += p[u];
for(int i = h[u]; i; i = nxt[i]) {
int v = to[i];
node.push(v);
}
}
cout << now - s << '\n';
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> s;
for(int i = 1; i <= n; i++) {
int fa;
cin >> p[i + 1] >> fa;
add(fa + 1, i + 1);
}
dfs(1);
work();
}