FOJ 2186 小明的迷宫(bfs+状压)
Problem 2186 小明的迷宫
Accept: 54 Submit: 162
Time Limit: 1000 mSec Memory Limit : 32768 KB
Problem Description
小明误入迷宫,塞翁失马焉知非福,原来在迷宫中还藏着一些财宝,小明想获得所有的财宝并离开迷宫。因为小明还是学生,还有家庭作业要做,所以他想尽快获得所有财宝并离开迷宫。
Input
有多组测试数据。
每组数据第一行给出两个正整数n,m(0<n,m<=100)。代表迷宫的长和宽。
接着n行,每行m个整数。正数代表财宝(财宝的个数不超过10);负数代表墙,无法通过;0代表通道。
每次移动到相邻的格子,所花费的时间是1秒。小明只能按上、下、左、右四个方向移动。
小明的初始位置是(1,1)。迷宫的出口也在(1,1)。
Output
输出获得所有财宝并逃出迷宫所花费的最小时间,如果无法完成目标则输出-1。
Sample Input
3 3 0 0 0 0 100 0 0 0 0 2 2 1 1 1 1
Sample Output
4 4
Source
FOJ有奖月赛-2015年03月
题目:小明的迷宫
题意:一个n*m的迷宫,每个格子里有一个值。
正数代表财宝(财宝的个数不超过10);负数代表墙,无法通过; 0代表通道;
每次移动到相邻的格子,所花费的时间是1秒.小明只能按上下左右四个方向移动。
小明的初始位置是(1,1),迷宫的出口也在(1,1).
求获得所有财宝并逃出迷宫所花费的最小时间,如果无法完成目标则输出-1.
思路:BFS + 状压DP
dp[i][j]表示小明访问完i代表的几个宝藏以后,处在第j个宝藏所在房间时,总共花费的时间
用两个BFS预处理从第i个宝藏到第j个宝藏所在房间的最小时间dist[i][j],和起点到各个宝藏的时间mint[i][j]
然后用状压处理dp
最后要记得加上返回(1,1)的时间
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<queue>
using namespace std;
const int N = 110;
const int INF = 1e8; // 定义无穷大
struct point
{
int x;
int y;
int time;
};
int map[N][N],dp[N][1<<11];
int mint[N][N]; //记录到达某点的最短时间
int dist[N][N]; //记录财宝之间的最短时间
queue<point>q;
int X[4]= {-1,1,0,0};
int Y[4]= {0,0,-1,1};
int n,m,cnt,dui[N][N];//dui[i][j]=k数组表示i,j坐标有一个编号为k的财宝
point start,end,no[20];
int bfs(int xx,int yy)//单点全图bfs
{
memset(mint,-1,sizeof(mint));
while(!q.empty())q.pop();
start.x = xx,start.y = yy;
mint[start.x][start.y] = 0;
start.time = 0;
q.push(start);
point hd,next;
int res = 0;
while (!q.empty())
{
hd = q.front();
q.pop();
//printf("## x = %d , y = %d , !!%d\n",hd.x,hd.y,res);
if(map[hd.x][hd.y] > 0)res += map[hd.x][hd.y];//这个要放这,放for里会错!
for(int k=0; k<4; k++)
{
next.x = hd.x+X[k];
next.y = hd.y+Y[k];
next.time = hd.time + 1;
int bao = map[next.x][next.y];
if(bao >= 0 && next.x >= 1 && next.x <= n && next.y >= 1 && next.y <= m && mint[next.x][next.y]==-1)
{
mint[next.x][next.y] = next.time;
q.push(next);
}
}
}
return res;
}
int vis[N][N];
int bfs2(int x1,int y1,int x2,int y2)//两点的最短时间
{
memset(vis,-1,sizeof(vis));
while(!q.empty())q.pop();
start.x = x1,start.y = y1;
vis[start.x][start.y] = 0;
start.time = 0;
q.push(start);
point hd,next;
while (!q.empty())
{
hd = q.front();
q.pop();
if(hd.x == x2 && hd.y == y2)return vis[x2][y2];
for(int k=0; k<4; k++)
{
next.x = hd.x+X[k];
next.y = hd.y+Y[k];
next.time = hd.time + 1;
int bao = map[next.x][next.y];
if(bao >= 0 && next.x >= 1 && next.x <= n && next.y >= 1 && next.y <= m && vis[next.x][next.y]==-1)
{
vis[next.x][next.y] = next.time;
q.push(next);
}
}
}
}
int main()
{
//freopen("in.txt","r",stdin);
while (scanf("%d%d",&n,&m)!=EOF)
{
int cnt = 0,sum = 0;
for(int i=1; i<=n; i++)
{
for(int j=1; j<=m; j++)
{
cin >> map[i][j];
if(map[i][j] > 0)
{
no[cnt].x = i,no[cnt].y = j,dui[i][j] = cnt++;
sum += map[i][j];
}
}
}
int res = bfs(1,1);
if(res < sum || map[1][1] < 0)
{
puts("-1");
continue;
}
if(sum == 0)
{
puts("0");
continue;
}
for(int i=0; i<cnt; i++)
for(int j=i; j<cnt; j++)
{
if(i == j)dist[i][j] = 0;
dist[i][j] = dist[j][i] = bfs2(no[i].x,no[i].y,no[j].x,no[j].y);
}
for(int i=0; i<cnt; i++)
for(int j=0; j<(1 << cnt); j++)
dp[i][j] = INF;
for(int i=0; i<cnt; i++)
dp[i][1<<i] = mint[no[i].x][no[i].y];//这里先加上从(1,1)出发到各点的时间
for(int j=0; j<(1 << cnt); j++)//这里和下一行的顺序一定不能反....找了一天的错误
for(int i=0; i<cnt; i++)
if(dp[i][j] != INF)
{
for(int k=0; k<cnt; k++)
{
if((1<<k) & j)continue;
dp[k][(1<<k)|j] = min(dp[k][(1<<k)|j],dp[i][j] + dist[i][k]);
}
}
int ans = INF;
for(int i=0; i<cnt; i++)
ans = min(ans , dp[i][(1<<cnt)-1] + mint[no[i].x][no[i].y]);//再加上回来的时间,会与出发点不同
printf("%d\n",ans);
}
return 0;
}
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<queue>
using namespace std;
const int N = 110;
const int INF = 1e8; // 定义无穷大
struct point
{
int x;
int y;
int time;
};
int map[N][N],dp[N][1<<11];
int mint[N][N]; //记录到达某点的最短时间
int dist[N][N]; //记录财宝之间的最短时间
queue<point>q;
int X[4]= {-1,1,0,0};
int Y[4]= {0,0,-1,1};
int n,m,cnt,dui[N][N];//dui[i][j]=k数组表示i,j坐标有一个编号为k的财宝
point start,end,no[20];
int bfs(int xx,int yy)//单点全图bfs
{
memset(mint,-1,sizeof(mint));
while(!q.empty())q.pop();
start.x = xx,start.y = yy;
mint[start.x][start.y] = 0;
start.time = 0;
q.push(start);
point hd,next;
int res = 0;
while (!q.empty())
{
hd = q.front();
q.pop();
//printf("## x = %d , y = %d , !!%d\n",hd.x,hd.y,res);
if(map[hd.x][hd.y] > 0)res += map[hd.x][hd.y];//这个要放这,放for里会错!
for(int k=0; k<4; k++)
{
next.x = hd.x+X[k];
next.y = hd.y+Y[k];
next.time = hd.time + 1;
int bao = map[next.x][next.y];
if(bao >= 0 && next.x >= 1 && next.x <= n && next.y >= 1 && next.y <= m && mint[next.x][next.y]==-1)
{
mint[next.x][next.y] = next.time;
q.push(next);
}
}
}
return res;
}
int vis[N][N];
int bfs2(int x1,int y1,int x2,int y2)//两点的最短时间
{
memset(vis,-1,sizeof(vis));
while(!q.empty())q.pop();
start.x = x1,start.y = y1;
vis[start.x][start.y] = 0;
start.time = 0;
q.push(start);
point hd,next;
while (!q.empty())
{
hd = q.front();
q.pop();
if(hd.x == x2 && hd.y == y2)return vis[x2][y2];
for(int k=0; k<4; k++)
{
next.x = hd.x+X[k];
next.y = hd.y+Y[k];
next.time = hd.time + 1;
int bao = map[next.x][next.y];
if(bao >= 0 && next.x >= 1 && next.x <= n && next.y >= 1 && next.y <= m && vis[next.x][next.y]==-1)
{
vis[next.x][next.y] = next.time;
q.push(next);
}
}
}
}
int main()
{
//freopen("in.txt","r",stdin);
while (scanf("%d%d",&n,&m)!=EOF)
{
int cnt = 0,sum = 0;
for(int i=1; i<=n; i++)
{
for(int j=1; j<=m; j++)
{
cin >> map[i][j];
if(map[i][j] > 0)
{
no[cnt].x = i,no[cnt].y = j,dui[i][j] = cnt++;
sum += map[i][j];
}
}
}
int res = bfs(1,1);
if(res < sum || map[1][1] < 0)
{
puts("-1");
continue;
}
if(sum == 0)
{
puts("0");
continue;
}
for(int i=0; i<cnt; i++)
for(int j=i; j<cnt; j++)
{
if(i == j)dist[i][j] = 0;
dist[i][j] = dist[j][i] = bfs2(no[i].x,no[i].y,no[j].x,no[j].y);
}
for(int i=0; i<cnt; i++)
for(int j=0; j<(1 << cnt); j++)
dp[i][j] = INF;
for(int i=0; i<cnt; i++)
dp[i][1<<i] = mint[no[i].x][no[i].y];//这里先加上从(1,1)出发到各点的时间
for(int j=0; j<(1 << cnt); j++)//这里和下一行的顺序一定不能反....找了一天的错误
for(int i=0; i<cnt; i++)
if(dp[i][j] != INF)
{
for(int k=0; k<cnt; k++)
{
if((1<<k) & j)continue;
dp[k][(1<<k)|j] = min(dp[k][(1<<k)|j],dp[i][j] + dist[i][k]);
}
}
int ans = INF;
for(int i=0; i<cnt; i++)
ans = min(ans , dp[i][(1<<cnt)-1] + mint[no[i].x][no[i].y]);//再加上回来的时间,会与出发点不同
printf("%d\n",ans);
}
return 0;
}