UVA 1490 Let the light guide us 题解
题意
有一个 \(n\times m\) 的网格,每个格子有两个值 \(a\) 和 \(b\)(原题中是 \(T\) 和 \(f\))。你需要在每一行选择一个格子,使得对于任意选择的两个格子 \((i, j), (i+1, k)\),满足 \(\lvert j - k \rvert \le b(i,j) + b(i + 1,k)\)。求所选格子的 \(a\) 之和的最小值。
\(n \le 100, m \le 5000, 0 \le b(i, j) \le 10^5\)。
思路
朴素 DP
首先,显然 \(b > 5000\) 是没有意义的(因为 \(\lvert j - k\rvert\) 是不可能大于 \(5000\) 的)。
然后可以设计出一个状态:\(f(i, j)\) 表示考虑了前 \(i\) 行,第 \(i\) 行选了第 \(j\) 列的格子,最小的代价。转移方程是这样的:
时间复杂度 \(O(nm^2)\)。
优化
这个状态基本上已经无法优化了(反正我不会),所以我们要在转移上下手。
我们看这个式子:
显然右边大于 \(0\),所以可以把它化成这样:(两个式子同时成立)
化简得:
接下来就比较神奇了。(鬼知道这是怎么想到的)
我们记 \(S_L = j - b(i, j)\),\(S_R = j + b(i, j)\),并记区间 \(S = [S_L, S_R]\)。
类似的,记 \(T_L = k - b(i - 1, k)\),\(T_R = k + b(i - 1, k)\),并记区间 \(T = [T_L, T_R]\)。
(显然 \(S_L \le S_R\),\(T_L \le T_R\))
那么上面的式子就可以写成:
那么这个式子和「\(S\) 与 \(T\) 相交」是等价的。
证明:
区间 \(S\) 与 \(T\) 不相交 等价于 \(S_R < T_L\) 或 \(T_R < S_L\),所以 \(S\) 与 \(T\) 相交 等价于:\( \begin{cases} S_R \ge T_L\\ T_R \ge S_L \end{cases} \)
这与原式相同。
然后就简单了。现在的问题是对于每个格子有一个区间 \([S_L, S_R]\),如果相邻两行的格子的区间相交,那么这两个格子就可以同时被选(即可以用上一行格子的 \(dp\) 值更新这一行的 \(dp\) 值)。
做法显然是对于每一行开一个线段树,将上一行的 \([T_L, T_R]\) 区间取 \(\min\),这一行的 \([S_L, S_R]\) 区间求 \(\min\),就是区间改区间查的问题了。
\(n\) 个线段树空间会炸,但其实可以只用开一个线段树。
时间复杂度 \(O(nm\log m)\)。
代码
// UVA1490
#include <cstdio>
#include <algorithm>
#include <cstring>
const int N = 100 + 5;
const int M = 5000 + 5;
const int ARR_V = 7000 + 5;
const int V_MAX = 6000;
const int INF = 0x3f3f3f3f;
int a[N][M], b[N][M];
int n, m;
int f[N][M];
struct SegmentTree {
int t[ARR_V << 3], lazy[ARR_V << 3];
void reset() {
memset(t, 0, sizeof(t));
memset(lazy, INF, sizeof(lazy));
}
void build() {
memset(t, INF, sizeof(t));
memset(lazy, INF, sizeof(lazy));
}
void set_lazy(int x, int v) { t[x] = std::min(t[x], v), lazy[x] = std::min(lazy[x], v); }
void lazy_down(int x) {
set_lazy(x << 1, lazy[x]), set_lazy(x << 1 | 1, lazy[x]);
lazy[x] = INF;
}
int query(int ql, int qr, int x = 1, int l = 1, int r = V_MAX << 1) {
if(ql > qr) return INF;
if(ql <= l && r <= qr) return t[x];
int mid = (l + r) >> 1, ret = INF;
lazy_down(x);
if(ql <= mid) ret = std::min(ret, query(ql, qr, x << 1, l, mid));
if(qr > mid) ret = std::min(ret, query(ql, qr, x << 1 | 1, mid + 1, r));
return ret;
}
void modify(int ql, int qr, int qv, int x = 1, int l = 1, int r = V_MAX << 1) {
if(ql > qr) return;
if(ql <= l && r <= qr) { set_lazy(x, qv); return; }
int mid = (l + r) >> 1;
lazy_down(x);
if(ql <= mid) modify(ql, qr, qv, x << 1, l, mid);
if(qr > mid) modify(ql, qr, qv, x << 1 | 1, mid + 1, r);
t[x] = std::min(t[x << 1], t[x << 1 | 1]);
}
} seg;
int main() {
while(scanf("%d%d", &n, &m) == 2 && (n | m)) {
for(int i = 1; i <= n; i++) for(int j = 1; j <= m; j++) scanf("%d", &a[i][j]);
for(int i = 1; i <= n; i++) for(int j = 1; j <= m; j++) scanf("%d", &b[i][j]), b[i][j] = std::min(b[i][j], 5000);
seg.reset();
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++)
f[i][j] = seg.query(j - b[i][j] + V_MAX, j + b[i][j] + V_MAX) + a[i][j];
seg.build();
for(int j = 1; j <= m; j++)
seg.modify(j - b[i][j] + V_MAX, j + b[i][j] + V_MAX, f[i][j]);
}
int ans = INF;
for(int j = 1; j <= m; j++) ans = std::min(ans, f[n][j]);
printf("%d\n", ans);
}
return 0;
}