力扣杯2020春季全国编程大赛#团队赛

第三题题解(BFS+状压dp)

题目描述(链接:题目链接):

我们得到了一副藏宝图,藏宝图显示,在一个迷宫中存在着未被世人发现的宝藏。

迷宫是一个二维矩阵,用一个字符串数组表示。它标识了唯一的入口(用 'S' 表示),和唯一的宝藏地点(用 'T' 表示)。但是,宝藏被一些隐蔽的机关保护了起来。在地图上有若干个机关点(用 'M' 表示),只有所有机关均被触发,才可以拿到宝藏。

要保持机关的触发,需要把一个重石放在上面。迷宫中有若干个石堆(用 'O' 表示),每个石堆都有无限个足够触发机关的重石。但是由于石头太重,我们一次只能搬一个石头到指定地点。

迷宫中同样有一些墙壁(用 '#' 表示),我们不能走入墙壁。剩余的都是可随意通行的点(用 '.' 表示)。石堆、机关、起点和终点(无论是否能拿到宝藏)也是可以通行的。

我们每步可以选择向上/向下/向左/向右移动一格,并且不能移出迷宫。搬起石头和放下石头不算步数。那么,从起点开始,我们最少需要多少步才能最后拿到宝藏呢?如果无法拿到宝藏,返回 -1 。

示例 1:

输入: ["S#O", "M..", "M.T"]

输出:16

解释:最优路线为: S->O, cost = 4, 去搬石头 O->第二行的M, cost = 3, M机关触发 第二行的M->O, cost = 3, 我们需要继续回去 O 搬石头。 O->第三行的M, cost = 4, 此时所有机关均触发 第三行的M->T, cost = 2,去T点拿宝藏。 总步数为16。

示例 2:

输入: ["S#O", "M.#", "M.T"]

输出:-1

解释:我们无法搬到石头触发机关

示例 3:

输入: ["S#O", "M.T", "M.."]

输出:17

解释:注意终点也是可以通行的。

限制:

1 <= maze.length <= 100
1 <= maze[i].length <= 100
maze[i].length == maze[j].length
S 和 T 有且只有一个
0 <= M的数量 <= 16
0 <= O的数量 <= 40,题目保证当迷宫中存在 M 时,一定存在至少一个 O 。

来源:力扣(LeetCode)
链接:题目链接

 

题目解析:

这道题目可以用转化为TSP问题去理解,其实路线就是S->O->M->O->M->O->M->...->T,我们可以把S也当作M处理,这样就变成了17个M点的TSP问题了,从起点出发,走完所有M点的最短路径,可以用经典的状压dp的方法计算。
所以问题的关键在于求出17个M点(包括了S点)之间的距离矩阵,而两个M之间的距离就是求M->O->M的最短距离,这样可以先BFS求任意两个M和O的距离,之后M->O->M就直接遍历40个O点,找最小,所以用三重循环就可以全部处理完任意两个M点的距离了(复杂度为17*17*40)。

最后的答案就是循环16个M点,对最后的dp数组,以点i为结尾,加上dist(i)->dist(T),取最小就是答案了


链接:力扣题解链接

代码:
 

class Solution {
public:
    struct Node{  //BFS用到,存点坐标
        int x,y,dist;
        bool operator<(const Node &t) const{
            return dist>t.dist;
        }
    };
    int get_dis(vector& maze,int k,int tox,int toy){ //BFS求两点间的距离
        queueque;
        for(int i=0;i=0&&tx=0&&ty& maze,int n){  //状压dp,传入的n表示M的数量+1(即M+一个S)
        const int INF = 0x3f3f3f3f;
        for(int i = 0 ; i <= n  ; i ++)
            for(int j = 0 ; j <= (1 << n) ; j ++) dp[i][j] = INF;
        dp[0][1] = 0;
       // for(int i = 0 ; i < n ; i ++) dp[i][1] = 0;
        for(int j = 1 ; j < (1 << n ) ; j ++){    //状态
            for(int i = 1 ; i < n ; i++){    //未被访问的点
                if(  !(j & ( 1 << (i)) )){//
                    for(int k = 0 ; k < n ; k ++){   //中间点
                        //if(k <= i) continue;
                        if(dp[k][j] != INF)    //中间点被访问过
                            dp[i][j+(1 << (i))] = min(dp[i][j+(1 << (i))] , dp[k][j]+mat[i][k]);
                    }
                }
            }
        }
       // cout << dp[n-1][(1<& maze) {
        n = maze.size(),m=maze[0].size();

        //预处理vec和vec2数组
        for(int i=0;iO->M)
        for(int i=0;iO->M)
    int lastn,lastm;               //T的坐标
    int vis[105][105];             //标记数组,BFS时候用
    int dp[18][(1<<18) + 5];       //状压dp,记得开18,我刚开始开17Wa了七八次
    int n,m;                       //迷宫的大小
    vector >vec,vec2;//vec保存M坐标,vec2保存O点坐标
};

 

你可能感兴趣的:(ACM)