[IOI2013]wombats
主要是想记录一下Maze 2D这种模型。
给一张 \(n \times m\) 的网格图,长成长条状,网格上的边有边权。人每次只能向上向下向右走,不能向左走。\(q\) 次询问,每次查从一个点走到另一个点的最短路。询问之间可能会穿插 \(t\) 次修改边权。
法一:DP
单次询问显然可以 \(n \times m\) 地DP:设 \(f(i,j)\) 表示从起点走到 \((i,j)\) 的最短路。\(g(i,j) = f(i,j-1) + lft(i,j)\)。(\(lft,rgt,up,dn\) 均为边权)然后有:
\[f(i,j) = \min_k(g(k,j) + dis((k,j),(i,j))
\]
一列一列地考虑即可。复杂度:\(O(t + nmq)\)。
法二:线段树套Floyd
因为 \(m\) 非常小,我们可以用线段树维护 \(L...R\) 列中 \(dis(i,j)\)。显然可以类似 Floyd 算法那样合并。这样的话复杂度就是 \(O((t+q)\log n \times m^3)\)。可以通过Maze 2D这道题。
法三:决策单调性优化Floyd
如果 \(m\) 不是那么小,我们每次 Floyd 的时候无法 \(m^3\) 地计算 \(dis(i,j)\)。不过我们发现,\((i,j)\) 的决策点一定比 \((i,j-1)\) 和 \((i-1,j)\) 靠后(假设边权均为正)。于是可以用决策单调性优化Floyd。复杂度:\(O((t+q)\log n \times m^2)\)。优化空间后可以通过[IOI2013]wombats这题。
(IOI2013wombats的代码)
const int block = 20;
int n, m;
int rgt[NN][N], dn[NN][N];
int g[N][N], h[N];
struct node {
int f[N][N];
int l, r;
node() { memset(f, 0x3f, sizeof(f)); l = r = 0; }
node(int L, int R) {
l = L, r = R;
memset(f, 0x3f, sizeof(f));
memset(h, 0, sizeof(h));
for (int p = 1; p <= m; ++p) {
for (int sum = 0, j = p; j; --j, sum += rgt[L][j]) f[p][j] = sum;
for (int sum = 0, j = p; j <= m; sum += rgt[L][j], ++j) f[p][j] = sum;
for (int i = L + 1; i <= R + 1; ++i) {
for (int j = 1; j <= m; ++j) h[j] = f[p][j] + dn[i - 1][j], f[p][j] = inf;
for (int mn = h[1], sum = 0, j = 1; j <= m; sum += rgt[i][j], ++j, MIN(mn, h[j] - sum))
MIN(f[p][j], mn + sum);
for (int mn = h[m], sum = 0, j = m; j; --j, sum += rgt[i][j], MIN(mn, h[j] - sum))
MIN(f[p][j], mn + sum);
}
}
}
}nd[NNN];
inline node operator +(const node &a, const node &b) {
node res;
memset(g, 0, sizeof(g));
for (int i = m; i; --i) {
for (int j = 1; j <= m; ++j) {
int d = j == 1 ? 1 : g[i][j - 1];
int u = i == m ? m : g[i + 1][j];
for (int k = d; k <= u; ++k) {
if (res.f[i][j] > a.f[i][k] + b.f[k][j]) {
res.f[i][j] = a.f[i][k] + b.f[k][j];
g[i][j] = k;
}
}
}
}
res.l = a.l; res.r = b.r;
return res;
}
int ls[NNN], rs[NNN], ttot, root;
inline void pushup(int cur) {
nd[cur] = nd[ls[cur]] + nd[rs[cur]];
}
void build(int L, int R, int &cur) {
cur = ++ttot;
if (R - L + 1 <= block) return nd[cur] = node(L, R), void();
int mid = (L + R) >> 1;
build(L, mid, ls[cur]); build(mid + 1, R, rs[cur]);
pushup(cur);
}
void modify(int L, int R, int pos, int cur) {
if (R - L + 1 <= block) return nd[cur] = node(L, R), void();
int mid = (L + R) >> 1;
if (pos <= mid) modify(L, mid, pos, ls[cur]);
else modify(mid + 1, R, pos, rs[cur]);
pushup(cur);
}