(BFS,状压DP)小明的迷宫

本文介绍了一道算法题“小明的迷宫”,利用BFS搜索算法结合状态压缩动态规划解决迷宫中寻找所有宝藏并返回起点的问题。通过分析问题特点,设计了高效的算法流程。

 Problem 2186 小明的迷宫

Accept: 86    Submit: 254
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 30 0 00 100 00 0 02 21 11 1

 Sample Output

44

 Source

FOJ有奖月赛-2015年03月


分析:设宝藏数目为N,用BFS将问题转化为N+1个点(N个宝藏加上起/终点(1,1))的完全图上求(从起点开始遍历所有宝藏后又回到起点的)最短路径(使用状压DP)。

定义dp[i][j]表示状态是j的时候走到的最后一个是i的最小消费时间

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int maxn = 105;
typedef long long LL;
const int INF = 0xfffffff;
int N, M;
int a[maxn][maxn], X[15], Y[15];
int dp[11][1 << 11];
bool vis[maxn][maxn];
int dis[15][15];
int d[maxn][maxn];
queue<int> q;
int dirx[4] = { 0, 0, 1, -1 };
int diry[4] = { 1, -1, 0, 0 };

int bfs(int x1, int y1)
{
	memset(vis, false, sizeof(vis));
	while (!q.empty())	q.pop();
	int sum = 0;

	vis[x1][y1] = true;
	if (a[x1][y1]>0)	sum += a[x1][y1];
	int t = x1*M + y1;
	q.push(t);
	while (!q.empty())
	{
		int u = q.front();	q.pop();
		int x = u / M, y = u % M;
		for (int i = 0; i < 4; i++)
		{
			int xx = x + dirx[i];
			int yy = y + diry[i];
			if (xx < 0 || N <= xx || yy < 0 || M <= yy || vis[xx][yy] == true || a[xx][yy]<0)	continue;
			vis[xx][yy] = true;
			if (a[xx][yy]>0)	sum += a[xx][yy];
			q.push(xx*M + yy);
		}
	}
	return sum;
}
int BFS(int x1, int y1, int x2, int y2)
{
	memset(vis, false, sizeof(vis));
	memset(d, 0, sizeof(d));
	while (!q.empty())	q.pop();

	vis[x1][y1] = true;
	if (x1 == x2&&y1 == y2)	return 0;//第一次访问
	int t = x1*M + y1;//用整形t保存二维图上的一点的坐标,注意M个列的下标需要取{0,1,2,...,M-1}
	q.push(t);
	while (!q.empty())
	{
		int u = q.front();	q.pop();
		int x = u / M, y = u % M;
		for (int i = 0; i < 4; i++)
		{
			int xx = x + dirx[i];
			int yy = y + diry[i];
			if (xx < 0 || N <= xx || yy < 0 || M <= yy || vis[xx][yy] == true || a[xx][yy]<0)	continue;//条件:没有越界,没有访问过,不是障碍
			vis[xx][yy] = true;
			d[xx][yy] = d[x][y] + 1;
			if (xx == x2&&yy == y2)	return d[xx][yy];
			q.push(xx*M + yy);
		}
	}
}
int main()
{
	freopen("f:\\input.txt", "r", stdin);
	while (~scanf("%d%d", &N, &M))
	{
		int cnt = 0, sum = 0;
		for (int i = 0; i < N;i++)
		for (int j = 0; j < M; j++)
		{
			scanf("%d", &a[i][j]);
			if (a[i][j]>0)	X[cnt] = i, Y[cnt++] = j, sum += a[i][j];
		}
		if (sum == 0)
		{
			printf("0\n");
			continue;
		}
		if (a[0][0] < 0)
		{
			printf("-1\n");
			continue;
		}
		if (bfs(0, 0) != sum)
		{
			printf("-1\n");
			continue;
		}

		X[cnt] = Y[cnt] = 0;
		for (int i = 0; i <= cnt; i++)
		for (int j = i; j <= cnt; j++)
		{
			if (i == j)
				dis[i][j] = 0;
			else
				dis[i][j] = dis[j][i] = BFS(X[i], Y[i], X[j], Y[j]);
		}
		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] = dis[cnt][i];

		for (int j = 1; j < (1<<cnt);j++)
		for (int i = 0; i < cnt; i++)//从dp[i][j]开始更新它的儿子
		{
			if (dp[i][j] == INF)continue;
			for (int k = 0; k < cnt; k++)//从宝藏i走到宝藏k
			{
				if ((j&(1 << i)) == 0)continue;
				if (i == k)continue;
				if ((j&(1 << k)) == 0)//可以进行第i个宝藏的更新
				{
					dp[k][j | (1 << k)] = min(dp[k][j | (1 << k)], dp[i][j] + dis[i][k]);
				}
			}
		}
		
		int ans = INF;
		for (int i = 0; i < cnt; i++)
			ans = min(ans, dp[i][(1 << cnt) - 1] + dis[cnt][i]);//
		printf("%d\n", ans);
	}
	return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值