1 条题解
-
0
自动搬运
来自洛谷,原作者为

saltFish
额搬运于
2025-08-24 23:00:57,当前版本为作者最后更新于2024-07-14 09:12:14,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
看到这一题我就想到了另一个题目,对比之下发现,这不就是 P7925 的加强版吗。
怪不得那么眼熟。思路
回归正题,我们考虑如何解决本题。题面很明显给出了一个森林,但并不妨碍,我们建出一个虚根 然后当有根树做就行。
首先,根据一个正常人的思维,我们只会在保证能够赚到钱的时候才会进入某个子树内,否则进去只会亏本,还不如不进去。
有了这个前提,我们希望对于每个点求出一个值 表示进入以 为根的子树时,我的手上至少要有 的钱数才能赚到钱,如果不可能赚到那么 。
假设我们已经求出了 ,对于一个拓展出的确定局面,我们一定是先取用能拓展到的 最小的点(这个可以考虑反证法证明,并不难)。这个事情可以交给堆来完成。考虑如何求答案,我们从根开始拓展,同时维护一个 表示当前钱数,一开始将根加入堆中,每次取出一个点,将它的贡献加入当前钱数,然后拓展出它的儿子,将它的儿子加入堆中。重复上述操作直至取出的点有 ,这表明现在不论如何拓展都不可能赚到到更多的钱,那么此时的 就是最大钱数,注意到题目要求的是利润,所以要减去 才是我们要求的答案。
上述过程为 ,没有问题。
接下来介绍本题的重点,如何求出 。
一个朴素的想法是,我们对于每一个点,采用与求答案类似的方法,从子树的根开始拓展整棵子树,并动态地维护我们的投入钱数 ,在 时,我们可以放心离开,否则就还要拓展下去,因为现在还是亏本的,但是不同的是,如果取出的点 有 ,由于现在还没有赚到足够的钱,我们必须进入 这个子树,但是又无法在 的子树内赚到更多钱了。那么为了赚到更多的钱,我们只有一个办法,加大投资。也就是我们更新 ,既然现在的 不够,那我们就加大投入让它够。具体的,我们将 加上 这样我们就可以将 提升至刚好能够到 ,能够放心赚钱了。如果自始至终都没有赚到,说明这个子树不可能赚到,令 即可。
现在我们就切掉了 P7925。这个过程明显是 的,能够通过我们前面提到的弱化版,但是难以通过本题(实测 TLE 两个点)。考虑优化,我们发现,在求 时,我们反复拓展了同一个子树,导致了极大的浪费,一个点的子树可能会被这个点的所有祖先扫个遍,所以优化这个过程是我们解题的关键。
我们发现,在求 拓展到一个点 时,我们一定会按照求 的顺序拓展 的子树,并且这个过程是必要的。否则进入 的子树就是无意义的(赚不到钱啊),不会选择进入它。
为了方便表述,我们将求完一个点的 之后堆中剩下点的点集称为边界弧。因为在这个点的子树内,边界弧之上的点都被拓展过了,而边界弧之下的点都还没被拓展。至于为什么是弧,
只是我在画图的时候画成了一条弧线。这样就好办了,我们在求完一个点的 之后,将它的边界弧存好,在祖先拓展到它的时候我们直接调用,将拓展至边界弧的过程直接跳过。具体的,我们把存好的边界弧直接合并到现在维护的堆中即可。这个过程需要一个可并堆。由于跳过了这个过程,所以我们还要对每个点维护一个 表示 节点拓展到边界弧所赚到的钱数,以此来更新我们维护的 。
至于可并堆的合并,我们可以采用启发式合并。
简单分析一下复杂度,边界弧只会向下推不会向上推,所以每个点只会被边界弧扫到一次,这个过程是 的,复杂度瓶颈在于可并堆合并,理论上界为 而且似乎不怎么能跑满,通过本题毫无压力。
那么我们就愉快地做完了这一题。本人比较懒,所以从 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(); }
- 1
信息
- ID
- 10525
- 时间
- 1000ms
- 内存
- 250MiB
- 难度
- 6
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者