Codeforces #677D Vanya and Treasure

tutorial

题目大意

有一个 $n \times m$ 的网格,网格上任意两个格点的距离定义为它们的曼哈顿距离。每个格点都有一个标号,第 $i$ 行第 $j$ 列的点标号为 $a_{ij}$($1\le a_{ij} \le p$) 。对于 $1$ 到 $p$ 之间的每个整数 $i$,至少存在一个格点标号为 $i$,并且只有一个点标号为 $p$ 。现欲从点 $(1,1)$ 依次经过标号为 $1,2,3\dots, p-1$ 的点,最后到达标号为 $p$ 的点,试求最短路的长度。

数据范围

$ 1 \le n, m \le 300 $
$ 1 \le p \le nm $

分析

How to approach the problem?

这道题可以看作是求分层图上两点间的最短路,分层图形如

level-graph.svg?sanitize=true

分层图是一类特殊的 DAG,所以可用动态规划来求解单源最短路问题,复杂度为 $O(E) = O(n^2m^2)$,这个复杂度是无法接受的。

在介绍更优的算法之前,先谈一谈对网格图的两种认识。

对于曼哈顿网格上的最短路问题,我们有两种方式来看待网格图。一是将网格图看作我们所关心的点的完全图 $K_c$ ($c$ 代表我们所关心的点的数目)或 $K_c$ 的某个子图,边权是两端点对应的格点的曼哈顿距离;二是将网格图看作「网格」,亦即一个 $n\times m$ 个点的无向图,每个点至多有 $4$ 个邻点,所有边长度都是 $1$ 。

grid.svg?sanitize=true

上述「分层图上的 DP 算法」是用第一种观点来看待网格图。

算法二 $\DeclareMathOperator{\cnt}{cnt}$

用 $\cnt(x)$ 表示网格中编号为 $x$ 的点的数目,用 $V_x$ 表示编号为 $x$ 的点的集合;即有 $\cnt(x) = |V_x|$ 。

当 $\cnt(i)\times \cnt(i+1) < nm$ 时,遍历 $V_i$ 和 $V_{i+1}$ 之间的边,进行松弛操作。

当 $ \cnt(i)\times \cnt(i+1) \ge nm$ 时,从 $V_i$ 中的所有点开始在网格图上 BFS

在网格图上 BFS 一次的复杂度为 $O(nm)$ 。下面证明

调用 BFS 的次数至多为 $\sqrt{nm}$

首先,我们有 $\sum_{i=1}^{p} \cnt(i) = nm $ 且 $\cnt(i) > 0$ 。

若 $\cnt(i) \cnt(i+1) \ge nm $ 则由基本不等式有

$$ \frac{\cnt(i) + \cnt(i+1)}{2} \ge \sqrt{\cnt(i) \cnt(i+1) } \ge \sqrt{nm} $$

假设有 $k$ 组相邻的标号 $(i_1, i_1 +1 ) , \dots, (i_j, i_j+1) \dots, (i_k, i_k+1)$ 使得 $\cnt(i_j) \cnt(i_j + 1) \ge nm $ 则有

$$ nm = \sum_{i=1}^{p} \cnt(i) > \sum_{j = 1}^{k} \frac{\cnt(i_j) + \cnt(i_j + 1)}{2} \ge k\sqrt{nm} $$

从而有 $ k < \sqrt{nm} $ 。

松弛操作的总次数不超过 $2nm\sqrt{nm}$

证明:首先,我们有

$$\cnt(i) \cnt(i+1) < mn \implies \min(\cnt(i), \cnt(i+1)) < \sqrt{nm} $$

并且

$$ \cnt(i) \cnt(i+1) =\min(\cnt(i), \cnt(i+1)) \times \max(\cnt(i), \cnt(i+1)) $$

因此

\begin{aligned}
\sum_i \cnt(i) \cnt(i+1) &= \sum_i \min(\cnt(i), \cnt(i+1)) \times \max(\cnt(i), \cnt(i+1)) \\
&< \sqrt{nm} \sum_i \max(\cnt(i), \cnt(i+1)) \\
&< \sqrt{nm} 2 nm
\end{aligned}

其中求和指标 $i$ 取遍使得 $ \cnt(i) \cnt(i+1) < nm $ 的标号。

至此,我们证明了此算法的复杂度为 $O(nm\sqrt{nm})$ 。

这个算法还有一个变体,可以达到同样的渐近复杂度。

当 $\cnt(i)\le \sqrt{nm}$ 且 $\cnt(i+1) \le \sqrt{nm}$ 时,遍历 $V_i$ 和 $V_{i+1}$ 之间的边,进行松弛操作;否则在网格图上进行 BFS 。

不难证明,松弛操作的总次数不超过 $2nm\sqrt{nm}$,调用 BFS 的次数不超过 $2\sqrt{nm}$ 。

算法三 $\DeclareMathOperator{\dp}{dp}$

假设我们已经算出到每个标号为 $i$ 的点的最短路长度,现在要据此算出到每个标号为 $i+1$ 的点的最短路长度。

用 $\dp[i][j]$ 表示从起点按要求走到点 $(i,j)$ 的最短路程。

  1. 用一个长为 $n$ 的布尔数组标记哪些行中有标号为 $i$ 点。
  2. 将每一列中标号为 $i+1$ 的点所在的行的行号存入一个列表(std::vector<int>)。
  3. 对于每个含有标号为 $i$ 的点的行 $y$,先从左到右遍历这一行,行至点 $(y,x)$ 时,对于第 $x$ 列中每个标号为 $i+1$ 的点 $p$,我们能够知道从起点经过 $(y,1),(y,2),\dots, (y,x)$ 中的某个标号为 $i$ 的点到达点 $p$ 的最短路程,用这个值更新 $dp[y][x]$ 。再从右到左进行这个过程

我们来分析此算法的复杂度。

在遍历行的过程中行经的格点总数不超过 $nm\cdot m$ 。

在遍历某一列中标号为 $i+1$ 的点的过程中每个标号为 $i+1$ 的点最多被访问 $n$ 次(即每一行中都有编号为 $i$ 的点),这部分访问的总节点数不超过 $n\cdot nm$

小结

为简便计,我们也把标号为 $i$ 的点称作 $i$ 类点。
我认为此算法针对「用 $i$ 类点计算 $i+1$ 类点」这个问题所采用方法是算法二中「在网格图上 BFS」的一个变体。做一形象描述就是:横向行走时,沿着网格图边走;纵向行走时,走「曼哈顿边」。

mixed-graph.svg?sanitize=true
图中红色节点表示 $i$ 类点,红色节点表示 $i+1$ 类点,直线表示网格边,曲线表示「曼哈顿边」。

算法四

算法四和算法三类似,它利用了网格的几何性质。

对于每个 $i+1$ 类点 $p$ 我们可以找出至多 $2(n+m)$ 个 $i$ 类点,使得点 $p$ 的 $\dp$ 值可只由这些点的 $\dp$ 值得到。实际上,只需要考虑每一行中横向上距离 $p$ 最近的两个点和每一列中纵向上距离 $p$ 最近的两个点。

算法五

用二维树状数组加速 DP 转移。想法上是暴力的,我不感兴趣。详见 TimonKnigge 在 tutorial 下的评论

总结

这道题目是两年前的老题,我花了两天时间写完这篇博客。现在写博客都只讲思路和分析复杂度,不贴代码。

我认为若能对复杂度比较敏感,那么写出的代码也不会差。

转载于:https://www.cnblogs.com/Patt/p/9182496.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值