Luogu P9128 [USACO23FEB] Fertilizing Pastures G 题解

Link

贪心好题。

考虑怎么计算时间,这是简单的:

  • \(T = 0\),相当于遍历整个树,每条边走 \(2\) 次去+回,时间为 \(2 \times (n - 1)\)
  • \(T = 1\),类似于上,但是有了一次选择一条从根到 \(x\) 的路径作为终点,路径上的边都只需要走一次,节省的时间为 \(dep_x\),显然我们要最大化节省的时间就要选一个深度最大的节点,此时时间为 \(2 \times (n - 1) - \max dep_x\)

怎么计算费用呢?费用取决于,到达每个节点的时间。遍历的顺序不同自然费用也不同。一个 key observation 是一旦进入某个子树,一定会走完再出来,如果不走完就出来,由于子树内的节点一直在生长,所以最优策略必定是对于每个节点我们都去决定其子节点的遍历顺序然后走完跳出来。

对于 \(T = 0\) 的讨论是经典的,对于节点 \(u\),记我们在遍历了若干节点之后已经花费了 \(ret\) 秒,记 \(siz(u), a(u), t(u), f(u)\) 分别表示 \(u\) 子树的大小,节点 \(u\) 的子树中 \(\sum_{i \in son_u} a_i\),遍历完 \(u\) 的子树并返回到 \(u\) 的时间,以及从 \(u\) 出发遍历完 \(u\) 的子树并返回 \(u\) 的最小费用。遍历子节点 \(v\) 时,\(v\) 子树已经增长了 \(ret\) 秒,遍历中花费 \(t(v)\) 秒,返回需要 \(2\) 秒,所以 \(f(u) \leftarrow f(u) + ret \times a_v + f(v)\) 之后更新 \(ret \leftarrow ret + t_v + 2\)。贪心排序过程使用邻项调整法,比较 \(x, y\)\(y, x\) 的差异,直接给出 \(cmp0\) 就是 \((t(u) + 2) \times a(v) \lt (t(v) + 2) \times a(u)\)。按照遍历顺序搜下去就可以了。

对于 \(T = 1\) 的讨论比较,麻烦的点在于那条不能返回的路径。贪心,选择一条从根到最深节点的链并定义为“主链”,特殊处理主链上的子树,其余的按照 \(T = 0\) 的情况处理。由此,定义 \(g(u)\) 表示从 \(u\) 出发并回到 \(u\) 内某一点而不返回到 \(u\) 的最小代价、\(f(u)\) 为从 \(u\) 出发,遍历子树同时必须返回到 \(u\) 的最小代价。我们需要维护最大深度以及候选为主链元素的节点。同样的,对于 \(cmp1\),我们使用邻项调整法,如果选择 \(u\) 为主链,\(v\) 需要返回,反之亦然。处理出主链元素之后单独 DP,其他的按照 \(T = 0\) 的情况处理即可。其实丢到搜索树上考虑类似于直接把要贪的主链元素丢到最后一个遍历的处理,我们做的就是尽可能将有着最长链的儿子往后推。

#include <bits/stdc++.h>

#define int long long

constexpr int N = 2e5 + 7;

int n, T, o;
int tim[N], siz[N], a[N], f[N];
int dep[N], mxd[N], g[N];

std::vector<int> adj[N], cdi[N];

bool cmp0(int x, int y) {
    return (tim[x] + 2) * a[y] < (tim[y] + 2) * a[x];
}

bool cmp1(int x, int y) {
    return (tim[x] + 2) * a[y] + g[x] + f[y] < (tim[y] + 2) * a[x] + g[y] + f[x];
}

void dfs0(int x) {
    siz[x] = 1; f[x] = a[x];
    for (int y : adj[x]) {
        dfs0(y);
        siz[x] += siz[y];
        a[x] += a[y];
    }
    tim[x] = 2 * (siz[x] - 1);
    std::sort(adj[x].begin(), adj[x].end(), cmp0);
    int res = 1;
    if (x == 1)
    	res = 0;
    for (int y : adj[x]) {
        f[x] = f[x] + res * a[y] + f[y];
        res += tim[y] + 2;
    }
}

void cal(int x) {
    mxd[x] = dep[x];
    for (int y : adj[x]) {
        dep[y] = dep[x] + 1;
        cal(y);
        mxd[x] = std::max(mxd[x], mxd[y]);
    }
}

void dfs1(int x) {
    siz[x] = 1; f[x] = g[x] = a[x];
    for (int y : adj[x]) {
        dfs1(y);
        siz[x] += siz[y];
        a[x] += a[y];
    }
    tim[x] = 2 * (siz[x] - 1);
    for (int y : adj[x]) {
        if (mxd[y] == mxd[1])
        	cdi[x].push_back(y);
    }
    std::sort(cdi[x].begin(), cdi[x].end(), cmp1);
    o = 0;
    if (cdi[x].size())
    	o = cdi[x].back();
    std::vector<int> vec;
    for (int y : adj[x])
        if (y != o) vec.push_back(y);
    std::sort(vec.begin(), vec.end(), cmp0);
    int res = 1;
    if (x == 1)
    	res = 0;
    for (int y : vec) {
        f[x] = f[x] + res * a[y] + g[y];
        res += tim[y] + 2;
    }
    if (o) f[x] = f[x] + res * a[o] + f[o];
    res = 1;
    if (x == 1)
    	res = 0;
    std::sort(adj[x].begin(), adj[x].end(), cmp0);
    for (int y : adj[x]) {
        g[x] += res * a[y] + g[y];
        res += tim[y] + 2;
    }
}

signed main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);

    std::cin >> n >> T;
    for (int i = 2; i <= n; i++) {
        int x; std::cin >> x >> a[i];
        adj[x].push_back(i);
    }
    if (T == 0) {
        dfs0(1);
        std::cout << 2 * n - 2 << " " << f[1];
    } else {
        cal(1);
        std::cout << 2 * n - 2 - mxd[1] << " ";
        dfs1(1);
        std::cout << f[1];
    }
    return 0;
}
posted @ 2025-11-11 10:04  起汐Moe  阅读(3)  评论(0)    收藏  举报