首先发现有用的点只有S,T,M,O,把他们提出来重新编号
通过bfs寻找两两之间的最短路dis。0 <= M的数量 <= 16,0 <= O的数量 <= 40
所以搜索量并不大。
问题是从S开始经过所有M后到达T,如果不考虑O,这就是经典的旅行商问题。
对于每个M点,达到之前必须经过一个O点,M,O并不多,这也就是说Mi经过O到Mj的最短路可以通过枚举O求得,记adis[Mi][Mj] = min(dis[Mi][Ok]+dis[Ok][Mj]),另外S到M同样处理,M到T不需要过O,adis[Mi][T]=dis[Mi][T] ;
我们发现对于adis,这就是旅行商问题,用状压dp就可以求解
int xx[4] = {0,0,1,-1};
int yy[4] = {1,-1,0,0};
int dp[1<<17][17];
class Solution {
public:
int id[200][200];
int dis[110][110];
int odis[110][110];
int inq[200][200];
void bfs(int x,int y,vector<string>& mase) {
int idd = id[x][y];
//dis[idd][idd] = 0;
int d[200][200];
memset(d,127/3,sizeof d);
queue<int> qx;
queue<int> qy;
qx.push(x);
qy.push(y);
d[x][y] = 0;
inq[x][y] = 1;
while(qx.size()) {
int tx = qx.front(),ty = qy.front();
qx.pop();
qy.pop();
inq[tx][ty] = 0;
for (int k = 0; k < 4; k++) {
int nx = tx+xx[k];
int ny = ty+yy[k];
if (nx<0 || ny<0 || nx>=mase.size() || ny>=mase[0].size()) continue;
if (mase[nx][ny] == '#') continue;
if (d[nx][ny] > d[tx][ty]+1) {
d[nx][ny] = d[tx][ty]+1;
if (!inq[nx][ny]) {
inq[nx][ny] = 1;
qx.push(nx);
qy.push(ny);
}
}
}
}
for (int i = 0; i < mase.size(); i++)
for (int j = 0; j < mase[i].size(); j++)
if (id[i][j] && id[i][j]!=idd) {
dis[idd][id[i][j]] = d[i][j];
//cout<<"dis::"<<idd<<" "<<id[i][j]<<" "<<d[i][j]<<"\n";
}
}
/* int find(int s,int d) {
if (dp[s] <= d) return;
dp[s] = d;
for (int i = 3; i <= 2+Mtp; i++)
if (s&(1<<i-1)==0)
dfs(s|(1<<i-1) , dp[s]+odis[]);
}*/
int minimalSteps(vector<string>& mase) {
int Mtp = 0,Otp = 0;
// int Sx,Sy,Tx,Ty;
for (int i = 0; i < mase.size(); i++)
for (int j = 0; j < mase[i].size(); j++) {
if (mase[i][j] == 'M') {
Mtp++;
} else if (mase[i][j] == 'O') {
Otp++;
}
}
int m = 0,o = 0;
// s -> 1
// T -> 2
//cout<<Mtp<<" "<<Otp<<"\n";
for (int i = 0; i < mase.size(); i++)
for (int j = 0; j < mase[i].size(); j++) {
if (mase[i][j] == 'M') {
m++;
id[i][j] = 2+m;
} else if (mase[i][j] == 'O') {
o++;
id[i][j] = 2+Mtp+o;
} else if (mase[i][j] == 'S') {
id[i][j] = 1;
} else if (mase[i][j] == 'T') {
id[i][j] = 2;
}
}
// for (int i = 0; i < mase.size(); i++) {
// for (int j = 0; j < mase[i].size(); j++)
// if(id[i][j])cout << i << " "<< j << " " << id[i][j] << "\n";
// }
//puts("");
memset(dis,127/3,sizeof dis);
memset(odis,127/3,sizeof odis);
for (int i = 0; i < mase.size(); i++)
for (int j = 0; j < mase[i].size(); j++) {
if (id[i][j])
bfs(i,j,mase);
// cout<<i<<" "<<j<<"\n";
}
//return -1;
//MOM
for (int j = 3+Mtp; j <= 2+Mtp+Otp; j++)
for (int k = 3; k <= 2+Mtp; k++)
odis[1][k] = min(odis[1][k],dis[1][j]+dis[j][k]);
for (int i = 3; i <= 2+Mtp; i++)
for (int j = 3+Mtp; j <= 2+Mtp+Otp; j++)
for (int k = 3; k <= 2+Mtp; k++)
if (i != k){
odis[i][k] = min(odis[i][k],dis[i][j]+dis[j][k]);
}
if (Mtp==0) {
return dis[1][2]==dis[0][0]?-1:dis[1][2];
}
for (int i = 3; i <= 2+Mtp; i++)
odis[i][2] = dis[i][2];
memset(dp,127/3,sizeof dp);
//dp[1] = 0;
//s 0
//m 1
//dp[1][0] = 0;
for (int l = 1; l <= Mtp; l++)
dp[1<<(l-1)][l] = odis[1][2+l];
for (int s = 1; s < (1<<Mtp); s++)
// if (s != (s&-s))
for (int l = 1; l <= Mtp; l++)
if (s&(1<<l-1))
for (int k = 1; k <= Mtp; k++)
if (l!=k && (s&(1<<k-1))) {
dp[s][l] = min(dp[s][l],dp[s^(1<<l-1)][k]+odis[k+2][l+2]);
}
int All = (1<<Mtp)-1,ans = dp[0][0];
for (int i = 1; i <= Mtp; i++)
ans = min(ans,dp[All][i]+odis[2+i][2]);
return ans==dp[0][0]?-1:ans;
}
};
我们得到了一副藏宝图,藏宝图显示,在一个迷宫中存在着未被世人发现的宝藏。
迷宫是一个二维矩阵,用一个字符串数组表示。它标识了唯一的入口(用 ‘S’ 表示),和唯一的宝藏地点(用 ‘T’ 表示)。但是,宝藏被一些隐蔽的机关保护了起来。在地图上有若干个机关点(用 ‘M’ 表示),只有所有机关均被触发,才可以拿到宝藏。
要保持机关的触发,需要把一个重石放在上面。迷宫中有若干个石堆(用 ‘O’ 表示),每个石堆都有无限个足够触发机关的重石。但是由于石头太重,我们一次只能搬一个石头到指定地点。
迷宫中同样有一些墙壁(用 ‘#’ 表示),我们不能走入墙壁。剩余的都是可随意通行的点(用 ‘.’ 表示)。石堆、机关、起点和终点(无论是否能拿到宝藏)也是可以通行的。
我们每步可以选择向上/向下/向左/向右移动一格,并且不能移出迷宫。搬起石头和放下石头不算步数。那么,从起点开始,我们最少需要多少步才能最后拿到宝藏呢?如果无法拿到宝藏,返回 -1 。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/xun-bao
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。