1. 题面梗概
快递员从 (Ax,Ay) 出发,要去 n 个点送外卖,最后回到 (Bx,By)。移动规则:只能往右 (x+1)、往上 (y+1) 或往下 (y−1) 走。
求完成所有任务的最短时间。
2. 逻辑推导
我们从移动规则切入这道题,发现 x 是固定单调递增的,那么水平运动的距离固定就是 Bx−Ax。
那么 y 轴呢?注意到我们每一列的 ymin 和 ymax 是必须经过的,所以,ymax−ymin 这段路也是固定的。
那么我们就只需要考虑每一列的端点处的转移了。
这里我采用自定义 sort 来进行排序,按 x 升序排序,x 相同的情况,按 y 升序排序。
这样一来某个 x 的区间 [L,R],ymin = p[L].y, ymax = p[R].y; 用双指针维护即可。
定义:
- 设 dpmin[i] 表示完成前 i 个 x 坐标后,停留在 ymin 的最小数值代价。
- 设 dpmax[i] 表示完成前 i 个 x 坐标后,停留在 ymax 的最小数值代价。
转移:简单分析后有四种情况:
- preymin→curymin→curymax
- preymin→curymax→curymin
- preymax→curymin→curymax
- preymax→curymax→curymin
针对转移写出代码即可。
3. 代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| #include <bits/stdc++.h> using namespace std; using ll = long long;
const int maxn = 2e5 + 5;
struct Point { ll x, y; } a[maxn], st, ed;
struct Seg { ll ymin, ymax; } last, cur;
ll dpmin[maxn], dpmax[maxn];
bool cmp( const Point &a, const Point &b ) { if( a.x == b.x ) return a.y < b.y; return a.x < b.x; }
void solve( ) { int n; cin >> n >> st.x >> st.y >> ed.x >> ed.y; for( int i = 1; i <= n; ++ i ) cin >> a[i].x; for( int i = 1; i <= n; ++ i ) cin >> a[i].y; a[n + 1] = { ed.x, ed.y };
sort( a + 1, a + n + 1, cmp );
int pos = 0; last = { st.y, st.y }; dpmin[0] = dpmax[0] = 0;
for( int l = 1; l <= n + 1; ) { int r = l; while( r <= n + 1 && a[r].x == a[l].x ) r ++;
cur = { a[l].y, a[r-1].y }; ll len = cur.ymax - cur.ymin; pos ++;
dpmin[pos] = min( dpmin[pos-1] + abs( last.ymin - cur.ymax ), dpmax[pos-1] + abs( last.ymax - cur.ymax ) ) + len; dpmax[pos] = min( dpmin[pos-1] + abs( last.ymin - cur.ymin ), dpmax[pos-1] + abs( last.ymax - cur.ymin ) ) + len; last = cur; l = r; }
ll ans = abs( ed.x - st.x ) + min( dpmin[pos], dpmax[pos] ); cout << ans << '\n'; }
int main( ) { ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); int _t = 1; cin >> _t; while( _t -- ) { solve( ); } return 0; }
|
4. 复盘