题目大意:
给出一个网格矩阵,用‘.’和‘#’表示网格的状态,'.'表示空地,可以通过该网格,'#'表示障碍,该网格不可通过。在该网格中存在一些地下道,给出地下道的入口坐标和出口坐标,要求不重复的经过所有的地下道,在走地下道的时候不计入时间,在从一个地下道的出口到另一个地下道的入口计入时间,且没移动到(上下左右)一个网格,记一个时间,初始位置可以在任意一个地下道的入口处。输出最少用时,如果不能走完所有的地下道,输出-1.
分析:
这是一道2014年西安邀请赛的最后一题,我只想到了BFS求每个地下道的出口到其他的地下道入口的最短距离,下面的就不止如何来解,想用深搜所有的行走路线,很明显会超时,在网上看了一下题解,这是一道BFS+DP的题目;
设dp[j][i]表示已经走完了j(包括第i个地下道)表示的地下道,最后走i地下道所用的最小时间。(注意:j是用十进制表示的二进制,对于二进制数的每一位,0表示没有走过,1表示已经走过,例如:j = 6,二进制表示为j = (110)2,表示第0个地下道没有走,第1,2个地下道已经走完,i表示的是地下道的标号)。
状态转移方程:
dp[ j | ( 1<<i ) ] [ i ] = min( dp [ j | ( 1<<i ) ] [ i ] , dp [ j ] [ k ] + dis [ k ] [ i ]);
dis[i][j]表示第i个地下道的出口到第j个地下道的入口的最短距离,如果不可达dis[i][j]=INF;
在求解dp[j|(1<<i)][i]的时候,要求dp[j][k]已经求过值,因为没有搞清楚这一点,贡献了一次WA.
代码:
#include <cstdio>
#include <cstring>
#include <queue>
#define INF 1234567
using namespace std;
struct Tunnels{
int x1, y1;
int x2, y2;
}t[20];
struct Node{
int x;
int y;
int value;
};
char grids[20][20];
int n, m, dp[1<<20][20];
int dis[20][20], tmp[20][20], vis[500];
int dirx[] = {-1, 0, 1, 0};
int diry[] = {0, 1, 0, -1};
void init() {
memset(vis, 0, sizeof(vis));
for(int i=0; i<n; i++) {
for(int j=0; j<n; j++) {
tmp[i][j] = INF;
}
}
}
int min(int a, int b) {
return a < b ? a : b;
}
void bfs(int v) {
Node start;
start.x = t[v].x2;
start.y = t[v].y2;
start.value = 0;
tmp[start.x][start.y] = start.value;
vis[start.x*n+start.y] = 1;
queue<Node> q;
q.push(start);
while(q.size() != 0) {
Node tmpN = q.front();
q.pop();
int tmpx, tmpy, tmpValue;
for(int i=0; i<4; i++) {
tmpx = tmpN.x + dirx[i];
tmpy = tmpN.y + diry[i];
tmpValue = tmpN.value + 1;
if(tmpx>=0 && tmpx<n && tmpy>=0 && tmpy<n &&
vis[tmpx*n+tmpy]==0 && grids[tmpx][tmpy]=='.') {
vis[tmpx*n+tmpy] = 1;
Node another;
another.x = tmpx; another.y = tmpy; another.value = tmpValue;
q.push(another);
tmp[another.x][another.y] = another.value;
}
}
}
}
void reqDis() {
for(int i=0; i<m; i++) {
init();
bfs(i);
for(int j=0; j<m; j++) {
if(i == j) dis[i][j] = INF;
else
dis[i][j] = tmp[t[j].x1][t[j].y1];
}
}
}
int ans;
void solve() {
int des = 1<<m;
for(int i=0; i<m; i++) {
for(int j=0; j<des; j++) {
dp[j][i] = INF;
}
}
for(int i=0; i<m; i++) {
dp[1<<i][i] = 0; // 1<<i的值表示已经走过了第i个点,所以dp[1<<i][i] = 0;
}
for(int j=0; j<des; j++) { //0~des-1为第一层循环,0~m-1为第二层循环,不可颠倒。
for(int i=0; i<m; i++) {
if(j & (1<<i)) continue; //j&(1<<i)表示第i个点已经在j这个状态里了
for(int k=0; k<m; k++) {
dp[j | (1<<i)][i] = min(dp[j | (1<<i)][i], dp[j][k] + dis[k][i]);
//如果两层循环颠倒,那么在求dp[j|(1<<i)][i]的时候,不能保证dp[j][k]是已经
//求过值的
//j | (1<<i)表示将第i个地下道加入到状态j中
}
}
}
ans = INF;
for(int i=0; i<m; i++) {
ans = min(ans, dp[des-1][i]);
}
}
int main() {
while(~scanf("%d%d", &n, &m)){
for(int i=0; i<n; i++) {
scanf("%s", grids[i]);
}
for(int i=0; i<m; i++) {
scanf("%d%d%d%d", &t[i].x1, &t[i].y1, &t[i].x2, &t[i].y2);
t[i].x1--; t[i].y1--;
t[i].x2--; t[i].y2--;
}
reqDis();
solve();
if(ans < INF)
printf("%d\n", ans);
else printf("-1\n");
}
return 0;
}