NOI 2026 见!

 

题解:P9755 [CSP-S 2023] 种树

\(x\) 天在地块 \(u\) 种下树后,那棵树在当天就会长 \(\max(b_u + x \times c_u, 1)\) 米,到完成任务的第 \(T\) 天,一共会长 \(\sum_{i=x}^T \max(b_u + i \times c_u, 1)\) 米。

可见每天至少长高 \(1\) 米,因此一定存在最优方案在 \([1, n]\) 天每天都种一棵树,再等他们都长到对应高度。

因此如果能在 \(t\) 天完成任务,那么 \(> t\) 天一定也能完成任务,树不可能缩水。因此考虑二分 \(t\)

接下来要解决一个可行性判定:能否安排一种种树的顺序,使得以 \(1\) 为根时,父亲一定要比儿子先种,且第 \(t\) 天,对于每个 \(u\),地块 \(u\) 上的树的高度 \(\ge a_u\)

既然问到顺序了,那么 \(n!\) 枚举所有可能的顺序,再检查是否合法,总是一个方法。但实际上所有合法的顺序就是树上的拓扑序,枚举所有拓扑序说不定能过所有满足 \(n \leq 20\) 的测试点。

任务安排类问题可以考虑每个任务的截止时间。本题中,地块 \(u\) 的截止时间即最大的 \(x\) 使得 \(\sum_{i=x}^T \max(b_u + i \times c_u, 1) \ge a_u\),因为 \(x\) 越大该式子越小。

设对于地块 \(u\) 上的树来说,这样的 \(x\)\(d_u\)。因为单调性,\(d_u\) 也是可以使用二分求解的。计算该式子也是简单的,因为由最多两段等差数列组成。

实际上可以把 \(d_u\)\(n\)\(\min\)。因为“一定存在最优方案在 \([1, n]\) 天每天都种一棵树“。这一步并不必要,只是为了讨论方便。

问题现在转换成了,是否存在一个树上的拓扑序,使得结点 \(u\) 在拓扑序列中的下标 \(\leq d_u\)

因为是拓扑序,所以父亲只能出现在每个儿子之前。就算有一条边 \((u, v)\),满足 \(u\)\(v\) 的父亲,且 \(d_u > d_v\)\(u\) 在拓扑序列也不能比 \(v\) 来得更后面。

因此以后序遍历的顺序,对所有 \(u\)\(d_u \gets \min(d_u, d_v - 1)\) 即可。这样既是必要的也是充分的,所以拓扑序的限制就被干掉了。

现在转换成的问题十分简洁:是否存在一个 \(\{1, 2, \dots, n\}\) 的排列 \(p\),使得对于每个 \(i\),有 \(p_i \leq d_i\)

这可以看作一个二分图完美匹配判定问题。左部点是结点 \(\{1, 2, \dots, n\}\),右部点是下标 \(\{1, 2, \dots, n\}\),左部点 \(u\) 与所有满足 \(i \leq d_u\) 的右部点 \(i\) 连一条无向边,问完美匹配是否存在。

Hall 定理指出,存在完美匹配当且仅当对于每个 \(i\)\(\# \{u \mid d_u \leq i \} \leq i\)

至此,二分的判定完成,整道题的思路便也完成。

#include <bits/stdc++.h>
using namespace std;

using ll = long long;
using lll = __int128_t;

constexpr int N = 100000 + 1;

ll a[N];
int b[N], c[N], cnt[N], ord[N], d[N];
vector<int> adj[N];

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

    int n;
    cin >> n;
    for (int i = 1; i <= n; ++i) {
        cin >> a[i] >> b[i] >> c[i];
    }
    for (int i = 1; i < n; ++i) {
        int u, v;
        cin >> u >> v;
        adj[u].push_back(v);
        adj[v].push_back(u);
    }

    int idx = 0;
    auto dfs = [&](auto &&self, int u) -> void {
        for (int v : adj[u]) {
            erase(adj[v], u);
            self(self, v);
        }
        ord[++idx] = u;
    };
    dfs(dfs, 1);
    auto chk = [&](int t) {
        for (int i = 1; i <= n; ++i) {
            cnt[i] = 0;
            auto calc = [&](int mid) -> lll {
                if (c[i] >= 0) {
                    return (b[i] + c[i] * (ll)mid + b[i] + c[i] * (ll)t) * (lll)(t - mid + 1) >> 1;
                }
                int ti = (b[i] - 1) / -c[i];
                if (ti < mid) {
                    return t - mid + 1;
                }
                ti = min(ti, t);
                return ((b[i] + c[i] * (ll)mid + b[i] + c[i] * (ll)ti) * (lll)(ti - mid + 1) >> 1) + t - ti;
            };
            int l = 1, r = n;
            while (l <= r) {
                int mid = (l + r) >> 1;
                if (calc(mid) >= a[i]) {
                    l = mid + 1;
                } else {
                    r = mid - 1;
                }
            }
            d[i] = r;
        }
        for (int i = 1; i <= n; ++i) {
            int u = ord[i];
            for (int v : adj[u]) {
                d[u] = min(d[u], d[v] - 1);
            }
            if (d[u] <= 0) {
                return false;
            }
            ++cnt[d[u]];
        }
        int sm = 0;
        for (int i = 1; i <= n; ++i) {
            if ((sm += cnt[i]) > i) {
                return false;
            }
        }
        return true;
    };

    int l = 1, r = 1e9;
    while (l <= r) {
        int mid = (l + r) >> 1;
        if (chk(mid)) {
            r = mid - 1;
        } else {
            l = mid + 1;
        }
    }
    cout << l << '\n';
    return 0;
}

posted on 2026-06-26 20:53  SkyWave2022  阅读(13)  评论(0)    收藏  举报

导航