1 条题解

  • 0
    @ 2025-8-24 23:00:57

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar saltFish

    搬运于2025-08-24 23:00:57,当前版本为作者最后更新于2024-07-14 09:12:14,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    看到这一题我就想到了另一个题目,对比之下发现,这不就是 P7925 的加强版吗。怪不得那么眼熟

    思路

    回归正题,我们考虑如何解决本题。题面很明显给出了一个森林,但并不妨碍,我们建出一个虚根 00 然后当有根树做就行。

    首先,根据一个正常人的思维,我们只会在保证能够赚到钱的时候才会进入某个子树内,否则进去只会亏本,还不如不进去。

    有了这个前提,我们希望对于每个点求出一个值 fuf_{u} 表示进入以 uu 为根的子树时,我的手上至少要有 fuf_{u} 的钱数才能赚到钱,如果不可能赚到那么 fu=+f_{u}=+\infty

    假设我们已经求出了 fuf_{u},对于一个拓展出的确定局面,我们一定是先取用能拓展到的 fuf_{u} 最小的点(这个可以考虑反证法证明,并不难)。这个事情可以交给堆来完成。考虑如何求答案,我们从根开始拓展,同时维护一个 nownow 表示当前钱数,一开始将根加入堆中,每次取出一个点,将它的贡献加入当前钱数,然后拓展出它的儿子,将它的儿子加入堆中。重复上述操作直至取出的点有 fu>nowf_{u}>now,这表明现在不论如何拓展都不可能赚到到更多的钱,那么此时的 nownow 就是最大钱数,注意到题目要求的是利润,所以要减去 ss 才是我们要求的答案。

    上述过程为 O(nlogn)O(n\log{n}),没有问题。

    接下来介绍本题的重点,如何求出 fuf_{u}

    一个朴素的想法是,我们对于每一个点,采用与求答案类似的方法,从子树的根开始拓展整棵子树,并动态地维护我们的投入钱数 fuf_{u},在 now>funow>f_{u} 时,我们可以放心离开,否则就还要拓展下去,因为现在还是亏本的,但是不同的是,如果取出的点 curcurfcur>nowf_{cur}>now,由于现在还没有赚到足够的钱,我们必须进入 curcur 这个子树,但是又无法在 uu 的子树内赚到更多钱了。那么为了赚到更多的钱,我们只有一个办法,加大投资。也就是我们更新 fuf_{u},既然现在的 nownow 不够,那我们就加大投入让它够。具体的,我们将 fuf_{u} 加上 fcurnowf_{cur}-now 这样我们就可以将 nownow 提升至刚好能够到 fcurf_{cur},能够放心赚钱了。如果自始至终都没有赚到,说明这个子树不可能赚到,令 fu=+f_{u}=+\infty 即可。

    现在我们就切掉了 P7925。这个过程明显是 O(n2logn)O(n^2\log{n}) 的,能够通过我们前面提到的弱化版,但是难以通过本题(实测 TLE 两个点)。

    考虑优化,我们发现,在求 fuf_{u} 时,我们反复拓展了同一个子树,导致了极大的浪费,一个点的子树可能会被这个点的所有祖先扫个遍,所以优化这个过程是我们解题的关键。

    我们发现,在求 fuf_{u} 拓展到一个点 curcur 时,我们一定会按照求 fcurf_{cur} 的顺序拓展 curcur 的子树,并且这个过程是必要的。否则进入 curcur 的子树就是无意义的(赚不到钱啊),不会选择进入它。

    为了方便表述,我们将求完一个点的 ff 之后堆中剩下点的点集称为边界弧。因为在这个点的子树内,边界弧之上的点都被拓展过了,而边界弧之下的点都还没被拓展。至于为什么是弧,只是我在画图的时候画成了一条弧线

    这样就好办了,我们在求完一个点的 ff 之后,将它的边界弧存好,在祖先拓展到它的时候我们直接调用,将拓展至边界弧的过程直接跳过。具体的,我们把存好的边界弧直接合并到现在维护的堆中即可。这个过程需要一个可并堆。由于跳过了这个过程,所以我们还要对每个点维护一个 gotugot_{u} 表示 uu 节点拓展到边界弧所赚到的钱数,以此来更新我们维护的 nownow

    至于可并堆的合并,我们可以采用启发式合并。

    简单分析一下复杂度,边界弧只会向下推不会向上推,所以每个点只会被边界弧扫到一次,这个过程是 O(nlogn)O(n\log{n}) 的,复杂度瓶颈在于可并堆合并,理论上界为 O(nlog2n)O(n\log^{2}{n}) 而且似乎不怎么能跑满,通过本题毫无压力。

    那么我们就愉快地做完了这一题。本人比较懒,所以从 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
    上传者