【状压DP】leetcode LCP13 寻宝

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

迷宫是一个二维矩阵,用一个字符串数组表示。它标识了唯一的入口(用 ‘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 。

【题目分析】
首先可以考虑搜索,也可以发现一定会T…

容易发现一个数据比较关键(虽然有点面向数据编程,,,):
M的数量为16各以内
我们其实可以考虑M的各种完成状态,利用0表示未完成,1表示完成。则假设有16个机关,那么1111111111111111=65535即表示全部完成的状态。
当然不止这个,我们还要知道最后是落在那个点
所以有状态设计:F[S][i],表示状态为S,当前在i号机关时的最短路。
于是我们可以枚举上一个机关是从哪来的,
于是有:F[S][i] = min(F[S’][j]+dist[i][j]){i∈S,j∈S’},其中S’为处在上一个机关时的状态,j为上一个机关的位置,dist[i][j]表示机关i到机关j在间隔一个重物下的最短路(可以通过一边枚举重物一边跑每个重物为起点的最短路预处理出来)
初始状态就需要起点到第一个机关的最短路,也可以。
最后需要各个机关到终点的最短路,也可以预处理。

代码量稍微有点大:

#include<cstdio>
#include<cstring>
#include<queue>
#define maxn 139
using namespace std;
int n, m, ko, km, f[(1<<16)+39][17];
char s[maxn][maxn];
int dx[] = {0,0,1,-1}, dy[] = {1,-1,0,0};
struct POINT{
	int x, y;
}S, T, M[17], o[50];
void bfs(POINT start, int dist[maxn][maxn]){
	memset(dist, 0x3f, sizeof(dist));
	int x, y;
	for(int i = 1; i < n+1; i++)
		for(int j = 1; j < m+1; j++)
			dist[i][j] = 0x3fffffff;
	queue<POINT> q;
	q.push(start);
	dist[start.x][start.y] = 0;
	while(!q.empty()){
		POINT head = q.front();
		x = head.x;y = head.y;
		q.pop();
		for(int i = 0; i < 4; i++){
			if(x+dx[i]>0&&x+dx[i]<n+1&&y+dy[i]>0&&y+dy[i]<m+1&&s[x+dx[i]][y+dy[i]]!='#'&&dist[x+dx[i]][y+dy[i]]>dist[x][y]+1){
				dist[x+dx[i]][y+dy[i]] = dist[x][y] + 1;
				q.push((POINT){x+dx[i], y+dy[i]});
			}
		}
	}
	return;
}
int distS[maxn][maxn], distO[maxn][maxn], distM[17][17], distT[maxn][maxn];
int min(int a, int b){return a<b?a:b;}
int main(){
	freopen("1.in", "r", stdin);
	//freopen("1.out", "w", stdout);
	scanf("%d%d", &n, &m);
	for(int i = 1; i < n+1; i++)scanf("%s", s[i]+1);
	for(int i = 1; i < n+1; i++)
		for(int j = 1; j < m+1; j++){
			if(s[i][j]=='S')S = (POINT){i, j};
			if(s[i][j]=='T')T = (POINT){i, j};
			if(s[i][j]=='O')o[++ko] = (POINT){i, j};
			if(s[i][j]=='M')M[++km] = (POINT){i, j};
		}
	bfs(S, distS);
	if(!km)return distS[T.x][T.y]>=0x3fffffff?-1:distS[T.x][T.y]; //还要考虑没有机关的情况
	memset(distM, 0x3f, sizeof(distM));
	memset(f, 0x3f, sizeof(f));
	for(int k = 1; k < ko+1; k++){
		bfs(o[k], distO);
		for(int i = 1; i < km+1; i++){
			f[1<<(i-1)][i] = min(f[1<<(i-1)][i], distS[o[k].x][o[k].y]+distO[M[i].x][M[i].y]);
			for(int j = i+1; j < km+1; j++){
				//if(i==j)continue;
				distM[i][j] = distM[j][i] = min(distM[i][j], distO[M[i].x][M[i].y]+distO[M[j].x][M[j].y]);
			}
		}
			
	}
	bfs(T, distT);
	for(int s = 1; s<(1<<km); s++)
		for(int i = 0; (1<<i) <= s; i++)
			if(s&(1<<i)){
				for(int j = 0; (1<<j) <= s; j++)
					if(((1<<j)&s)&&(i!=j))f[s][i+1] = min(f[s][i+1], f[s^(1<<i)][j+1]+distM[i+1][j+1]);
			}
	int ans = 0x3fffffff;
	for(int i = 1; i < km+1; i++)ans = min(ans, f[(1<<km)-1][i]+distT[M[i].x][M[i].y]);
	printf("%d", ans>=0x3f3f3f3f?-1:ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值