T6. 燃料

小明要开车从家里前往远方的城市 \(C\),他家到城市的路程是 \(L\) 公里。他的车每开 \(1\) 公里需要消耗 \(1\) 升汽油,车的油箱最多只能装 \(V\) 升汽油,出发前他的车是加满汽油的。
路途中有 \(n\) 个加油站,第 \(i\) 个加油站离小明家的路程是 \(x_i\) 公里,在这里可以花费 \(w_i\) 元加 \(v_i\) 升汽油且只能加一次。但是如果超过了油箱容量,就只能加满为止。
问小明到达城市 \(C\) 至少需要花费多少?

输出到达城市 \(C\) 需要花费的最小值。如果无法到达,输出 -1

限制:

  • \(1 \leqslant n \leqslant 2000\)
  • \(1 \leqslant V \leqslant 2000\)
  • \(1 \leqslant L \leqslant 10^9\)
  • \(0 \leqslant x_1 \leqslant x_2 \leqslant \cdots \leqslant x_n \leqslant L\)
  • \(0 \leqslant v_i \leqslant 10^9\)
  • \(0 \leqslant w_i \leqslant 10^9\)

算法分析

dp[i][j] 表示到第 \(i\) 个加油站且(加油前)油剩 \(j\) 升时的最小花费
初值:\(dp[0][V] = 0\),其他 \(dp = \infty\)
用求好的 dp[i][j]`` 去推出 dp[i+1][?]`` 的最小
\(0\) 个加油站看做是家 \(x_0 = 0\)
\(n+1\) 个加油站看做是终点 \(x_{n+1} = L\)

代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)

using namespace std;
using ll = long long;

const ll INF = 1e18;

int x[2005], v[2005], w[2005];
ll dp[2005][2005];

inline void chmin(ll& x, ll y) { if (x > y) x = y; }

int main() {
    int n, V, L;
    cin >> n >> V >> L;
    
    for (int i = 1; i <= n; ++i) {
        cin >> x[i] >> v[i] >> w[i];
    }
    x[n+1] = L;
    
    memset(dp, 0x3f, sizeof dp);
    
    dp[0][V] = 0;
    rep(i, n+1) {
        rep(j, V+1) {
            int t = x[i+1]-x[i];
            int nj;
            // 不加油直接出发,到下一站要消耗t
            nj = j-t;
            if (nj >= 0) chmin(dp[i+1][nj], dp[i][j]);
            // 加油后出发,到下一站剩余 min(j+v[i], V)-t
            nj = min(j+v[i], V)-t;
            if (nj >= 0) {
                chmin(dp[i+1][nj], dp[i][j]+w[i]);
            }
        }
    }
    
    ll ans = INF;
    rep(j, V+1) chmin(ans, dp[n+1][j]);
    
    if (ans == INF) ans = -1;
    cout << ans << '\n';
    
    return 0;
}

T7. 穿越战场

有一个长方形战场,战场地图被分成 \(n\)\(m\) 列一共 \(n \times m\) 个方格区域。从北向南编号为 \(1 \sim n\) 行,从西向东编号为 \(1 \sim m\) 列。
战场上有若干个格子内有敌人的哨所,第 \(i\) 个哨所在第 \(x_i\) 行第 \(y_i\) 列。
你现在要和东北方的大部队会和。你的任务是从战场南边界(第 \(n\) 行)的任意位置进入战场,在 \(T\) 分钟内,从北边界(第 \(1\) 行)的一个尽量靠东的位置离开战场。战场是一个平原,每次移动,你都可以从当前所在格子移动到上下左右的格子,进入战场的时刻定为第 \(1\) 分钟,\(1\) 次移动花费 \(1\) 分钟的时间。只要到达北边界的时间小于等于 \(T\) 分钟就可以。
移动过程中,离敌人的哨所越近,被发现的风险越高。我们定义整个移动路线的安全程度,等于途中离敌人哨所距离的最小值。我们定义距离为行列差的绝对值之和:\(x_1\)\(y_1\) 列与 \(x_2\)\(y_2\) 列的距离为 \(|x_1 - x_2| + |y_1 - y_2|\)
在满足 \(T\) 分钟内离开战场的前提下,求你的路线的安全程度的最大值,安全程度最大的路线的终点的列坐标。如果有多条路线安全程度都是最大,输出其中列坐标最大的终点。

限制:

  • \(2 \leqslant n, m \leqslant 1000\)
  • \(n \leqslant T \leqslant n \times m\)
  • \(1 \leqslant x_i \leqslant n\)
  • \(1 \leqslant y_i \leqslant m\)

算法分析

二分安全度 \(d\)
check(d) 有没有安全度 \(d\) (也就是路线上每格与哨所距离 \(\geqslant d\))且时间 \(\leqslant T\) 的路线
路线上每格与哨所距离 \(\geqslant d\),我们可以不走与哨所距离 \(< d\) 的格子,判断能否在 \(\leqslant T\) 的时间内到达北边界
以南边界上所有点为起点,做一遍多源bfs,求出所有北边界上点的最短路,之后找一下北边界上有无最短路 \(\leqslant T\) 的格子,返回其中最大的列坐标,没找到返回 \(0\)
在 check 中要判断每个格子与哨所距离是否 \(< d\),如果在 check 中现场判断的话:\(O(n^2m^2)\)
只能在二分前,先计算出每个点与哨所的最短距离(最短路)
以所有哨所为起点,做一次多源bfs

最后的时间复杂度为 \(O(\log (n+m) \cdot (n+m))\)