bzoj 3379 - [USACO2004] 交作业
Description
一个数轴上有 \(n \le 1000\) 个位置, 每个位置有一个时间 \(t_i\)
要求在 时刻 \(t_i\) 后, 至少经过该位置一次. (去交作业)
求从 \(0\) 号点出发前往 \(B\) 号点, 满足上述条件的最小需要时间
Solution 1
CQzhangyu的做法. 非常的妙啊
\(\ge t\) 不好做, 走过的位置以后可能又会突然冒出一个人, 决策的顺序位置.
考虑反着来, 先二分答案 \(Ans\), 并从 \(B\) 往 \(0\) 走.
这样限制条件变成了 \(\le Ans - t_i\), 这样就之用在限制时间内经过一次该点即可
直接使用区间dp, 复杂度 \(O(n^2\log n)\)
Solution 2
考虑初始状态 \([1, n]\) 区间均未交作业
接着你会进去区间内乱走一通, 交一些作业. 且此时你一直没有交边界的作业
但是边界的作业迟早是要交的, 两个边界中, 必有一个是 先被到达的
不妨设 \([a,b]\) 先到达了 \(a\), 那么接下来, \(b\) 一定是要过去的, 中间的点都是必须被经过的.
而这些中间被经过的, 若是在乱走到达 \(a\) 之前交了作业, 将其换至 到 \(a\) 后往 \(b\) 走的过程中再交是没有影响的.
于是, 最优的决策, 一定是选择区间的一端 先交作业
接着就可以使用区间dp, 区间从大往小走就好了
Notice
坐标有 \(0\)
Code
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <cmath>
#include <algorithm>
#define rep(i, a, b) for (int i = (a), _ = (b); i <= _; ++ i)
#define per(i, a, b) for (int i = (a), _ = (b); i >= _; -- i)
#define For(i, a, b) for (int i = (a), _ = (b); i < _; ++ i)
#define ri rd<int>
using namespace std;
const int N = 1007;
const int INF = 1e9 + 7;
template<class T> inline T rd() {
bool f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = 0;
T x = 0; for (; isdigit(c); c = getchar()) x = x * 10 + c - 48; return f ? x : -x;
}
int m, n, cur;
int t[N];
int f[N][N];
int lf, rt;
int main() {
#ifndef ONLINE_JUDGE
freopen("a.in", "r", stdin);
#endif
m = ri(), n = ri(), cur = ri() + 1;
lf = n + 1, rt = 0;
rep (i, 1, m) {
int x = ri() + 1, y = ri();
t[x] = max(t[x], y);
lf = min(lf, x), rt = max(rt, x);
}
memset(f, 0x3f, sizeof f);
f[lf][rt] = max(lf - 1, t[lf]), f[rt][lf] = max(rt - 1, t[rt]);
per (l, rt-lf-1, 0) rep (i, lf, rt - l) {
int j = i + l;
f[i][j] = min(f[i][j], max(t[i], min(f[i-1][j] + 1, f[j+1][i] + ((j + 1) - i))));
f[j][i] = min(f[j][i], max(t[j], min(f[i-1][j] + (j - (i - 1)), f[j+1][i] + 1)));
}
int res = INF;
rep (i, lf, rt) res = min(res, f[i][i] + abs(i - cur));
printf("%d\n", res);
return 0;
}