[CF2045D] Aquatic Dragon

方便起见,下文用水陆空三栖 代替 dragon,用 油量 代替 stamina。油箱容量没有上限。

记一次 walk 操作花费的时间为 \(a\),一次 swim 花费 \(b\),一次 fly 花费 \(c\)\(s_i=\sum_{j=1}^i p_i\)

显然,人不会到车左边,车不会后退。

假设当前人和车都在第 \(i\) 个点,油箱有 \(j\) 升油。

  • fly 到 \(i+1\),油量变为 \(p_{i+1}\),代价为 \(c\)
  • \(j \ge d\),直接 swim,油量 \(j-d+p_{i+1}\),代价为 \(b\)
  • \(j < d\),假设人 walk 到 \(t\) 之后返回 \(i\),显然在 \([i, t - 1]\) 之内车只能 swim。
    • \(j + s_t - s_i - d(t - i) \ge 0\),那么在 \(t-1\rightarrow t\) 时可以 swim。不等号左边为新的油量,代价为 \((2a+b)(t-i)\)
    • \(j + s_t - s_i - d(t - i - 1) > 0\),那么在 \(t-1\rightarrow t\) 时可以 fly。到达 \(t\) 后油量归 \(0\),且代价为 \((2a+b)(t-i)-b+c\)

考虑优化上述暴力。

如果认为经过一次 fly 之后得到的 \((i, 0)\)\((i, p_i)\) 是特殊的,那么只有 \(O(n)\) 个特殊状态,其他状态一定通过某个特殊状态让车一直 swim 得到。设 \(t_j \in \{0, p_j\}\),那么从 \((j, t_j)\) 车只 swim 只能转移到一些合法的 \((i, s_i - s_j + t_j)\)

从前往后 DP,尝试使用数据结构维护每个特殊状态 swim 到 \(i\) 的代价。并在外部记录偏移量:默认所有的 \(i-1 \rightarrow i\) 过程都经过水路,且都经过了往返 walk 拾取的过程。

对于 \(i\) 处新产生的特殊状态,计算初始代价,转移条件:

  • 当前 \((i, 0)\) 来自之前的 \((j, t_j)\),需要 \((s_i - s_j + t_j) - d(i - j - 1) > 0\)
  • 当前 \((i, p_i)\) 来自之前的 \((j, t_j)\),需要 \((s_{i-1} - s_j + t_j) - d(i - j - 1) > 0\)

对于已有的所有特殊状态,如果 \((s_i - s_j + t_j) - d(i - j)\ge 0\),这意味着 \((j, t_j)\) 可在 \(i - 1 \rightarrow i\) 处不需要经过往返 walk 的拾取,不需要加上额外预定的 \(2a\)

观察发现,三个不等式都可以变成 \(s_j - t_j - d_j\) 小于等于某个数的形式,于是按此排序离散化后,作为线段树的下标即可。

最终答案考虑最后一步是 swim 还是 fly。

一些细节参考实现。

int n, m;
LL a, b, c, d, p[MAXN], sp[MAXN], val[MAXN], dsc[MAXN<<1];

int main() {
    read(n, d, b, c, a);
    for (int i = 1; i <= n; ++i) read(p[i]), sp[i] = sp[i-1] + p[i];
    for (int i = 1; i <= n; ++i)
        dsc[++m] = sp[i-1] - i * d, dsc[++m] = sp[i] - i * d;
    sort(dsc+1, dsc+m+1), m = unique(dsc+1, dsc+m+1)-dsc-1;
    auto findle = [&](LL v) -> int { return upper_bound(dsc+1, dsc+m+1, v)-dsc-1; };
    SGT::build(1, 1, m), SGT::mdf(1, 1, m, findle(-d), 0);
    LL ans = INF;
    for (int i = 2; i <= n; ++i) {
        LL u = SGT::qry(1, 1, m, 1, findle(sp[i]-d*(i-1)-1)) + c - b;
        LL v = SGT::qry(1, 1, m, 1, findle(sp[i-1]-d*(i-1)-1)) + c - b;
        SGT::mdf(1, 1, m, findle(sp[i]-i*d), u);
        SGT::mdf(1, 1, m, findle(sp[i-1]-i*d), v);
        SGT::upd(1, 1, m, 1, findle(sp[i-1]-d*i), -2 * a);
    }
    ans = min(ans, SGT::qry(1, 1, m, 1, findle(sp[n]-d*n)));
    write(ans + (n - 1) * (a * 2 + b));
    return 0;
}

一开始讨论漏了向前拾取一段到 \(t\) 之后,在 \(t-1 \rightarrow t\) 使用 fly 的情况,在极不清醒的情况下调了一上午。

4 10 10 100 1
5 1 5 1
ans = 216
posted @ 2025-06-13 16:50  SZwinsun  阅读(11)  评论(0)    收藏  举报