题目描述
1944 年,特种兵麦克接到国防部的命令,要求立即赶赴太平洋上的一个孤岛,营救被敌军俘虏的大兵瑞恩。
瑞恩被关押在一个迷宫里,迷宫地形复杂,但幸好麦克得到了迷宫的地形图。
迷宫的外形是一个长方形,其南北方向被划分为 N 行,东西方向被划分为 M 列, 于是整个迷宫被划分为 N×M 个单元。
每一个单元的位置可用一个有序数对 (单元的行号, 单元的列号) 来表示。
南北或东西方向相邻的 2 个单元之间可能互通,也可能有一扇锁着的门,或者是一堵不可逾越的墙。
注意: 门可以从两个方向穿过,即可以看成一条无向边。
迷宫中有一些单元存放着钥匙,同一个单元可能存放 多把钥匙,并且所有的门被分成 P 类,打开同一类的门的钥匙相同,不同类门的钥匙不同。
大兵瑞恩被关押在迷宫的东南角,即 (N,M) 单元里,并已经昏迷。
迷宫只有一个入口,在西北角。
也就是说,麦克可以直接进入 (1,1) 单元。
另外,麦克从一个单元移动到另一个相邻单元的时间为 1,拿取所在单元的钥匙的时间以及用钥匙开门的时间可忽略不计。
试设计一个算法,帮助麦克以最快的方式到达瑞恩所在单元,营救大兵瑞恩。
输入格式
第一行有三个整数,分别表示 N,M,P 的值。
第二行是一个整数 k,表示迷宫中门和墙的总数。
接下来 k 行,每行包含五个整数,Xi1,Yi1,Xi2,Yi2,Gi:当 Gi≥1 时,表示 (Xi1,Yi1) 单元与 (Xi2,Yi2) 单元之间有一扇第 Gi 类的门,当 Gi=0 时,表示 (Xi1,Yi1) 单元与 (Xi2,Yi2) 单元之间有一面不可逾越的墙。
接下来一行,包含一个整数 S,表示迷宫中存放的钥匙的总数。
接下来 S 行,每行包含三个整数 Xi1,Yi1,Qi,表示 (Xi1,Yi1) 单元里存在一个能开启第 Qi 类门的钥匙。
输出格式
输出麦克营救到大兵瑞恩的最短时间。
如果问题无解,则输出 -1。
数据范围
|Xi1−Xi2|+|Yi1−Yi2|=1,
0≤Gi≤P,
1≤Qi≤P,
1≤N,M,P≤10,
1≤k≤150
输入样例:
4 4 9
9
1 2 1 3 2
1 2 2 2 0
2 1 2 2 0
2 1 3 1 0
2 3 3 3 0
2 4 3 4 1
3 2 3 3 0
3 3 4 3 0
4 3 4 4 0
2
2 1 2
4 2 1
输出样例:
14
解题思路
这是一道迷宫类型的最短路问题,相比之前遇到的迷宫类型的最短路问题多了一些条件(墙、门和钥匙),之前那些问题都是采用DP的方式直接进行求解,所以这道题也就想到了是否能够采用DP的方式来进行求解呢。很显然,直接采用之前二维坐标状态表示的方式来进行求解是行不通的,因为这道题中不只有二维坐标,还有钥匙的限制条件(墙和门属于地图的状态)。DP问题中,当状态还无法表示时,通常都是采用“加维”的方式,在这道题里,我们可以通过多加一维状态(在图论中,这种方式也就被叫做拆点了),表示所拥有钥匙的状态,因为钥匙的种类数顶多为10种,并且每把钥匙可以使用多次,所以只需要能够表示出拥有哪些钥匙就可以了,因此采用二进制的方式来表示所拥有钥匙的状态,即将状态表示为:DP[i][j][k],代表处于位置i、j,并且所拥有的钥匙状态为k(拥有第几类钥匙对应第i - 1位就为1,反之为0)
为了方便,将坐标的状态表示压缩为一维,即将坐标按从左到右再从上到下依次编号为1~N。
接下来考虑状态转移方程,每个位置的状态都可以从它上下左右四个状态转移而来,并且可以发现,从某一个位置走出,之后是完全有可能会重新回到该位置的,也就是该图是不满足拓扑序的,因此无法采用DP的方式来进行求解,但是我们可以采用图论中最短路问题的方式来进行求解,将相邻两个位置之间的道路状态(墙、门和通路)抽象为边,墙表示两个位置之间没有边,通路表示两个位置之间的道路权值为0,门的权值为相应钥匙代号。通过上述的方式,就可以将该迷宫地图抽象为一个平时图论中最短路问题的图了,而dp的状态表示就抽象为图中的每一个节点,然后再在这张图上进行求解。
通过分析可以发现,抽象之后的图的节点关系只有两种:
(1)所处位置有钥匙,那么拿起所有的钥匙(很显然,拿起所有的钥匙一点都不亏,因为拿钥匙不花费时间,如果只拿其中的一些,那么有可能之后还需要返回来重新拿起之前没有拿的那一部分),状态转化为拿起钥匙后的状态,这种状态转移的时间消耗为0;
(2)从所处位置走到下一个位置(上下左右),状态转化为下一个位置的状态,这种状态转移的时间消耗为1
在权值只有0和1的图上做最短路问题,那么可以直接采用双端队列的方式进行求解。
思路来源:AcWing 算法提高课
代码
#include <cstring>
#include <iostream>
#include <algorithm>
#include <deque>
#include <set>
#include <utility>
#define x first
#define y second
using namespace std;
typedef pair<int, int> PII;
const int N = 11, M = 4 * N * N, P = 10;
int n, m, p;
int h[N * N], e[M], w[M], ne[M], idx;