状压DP + 最短路 HDU 4856

探讨如何通过状态压缩动态规划解决迷宫中的隧道探索问题,实现寻找穿过所有隧道的最短路径。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Tunnels

Time Limit: 3000/1500 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 2398    Accepted Submission(s): 728


Problem Description
Bob is travelling in Xi’an. He finds many secret tunnels beneath the city. In his eyes, the city is a grid. He can’t enter a grid with a barrier. In one minute, he can move into an adjacent grid with no barrier. Bob is full of curiosity and he wants to visit all of the secret tunnels beneath the city. To travel in a tunnel, he has to walk to the entrance of the tunnel and go out from the exit after a fabulous visit. He can choose where he starts and he will travel each of the tunnels once and only once. Now he wants to know, how long it will take him to visit all the tunnels (excluding the time when he is in the tunnels).
 

Input
The input contains mutiple testcases. Please process till EOF.
For each testcase, the first line contains two integers N (1 ≤ N ≤ 15), the side length of the square map and M (1 ≤ M ≤ 15), the number of tunnels.
The map of the city is given in the next N lines. Each line contains exactly N characters. Barrier is represented by “#” and empty grid is represented by “.”.
Then M lines follow. Each line consists of four integers x1, y1, x2, y2, indicating there is a tunnel with entrence in (x1, y1) and exit in (x2, y2). It’s guaranteed that (x1, y1) and (x2, y2) in the map are both empty grid.
 

Output
For each case, output a integer indicating the minimal time Bob will use in total to walk between tunnels.
If it is impossible for Bob to visit all the tunnels, output -1.
 

Sample Input
5 4 ....# ...#. ..... ..... ..... 2 3 1 4 1 2 3 5 2 3 3 1 5 4 2 1
 

Sample Output
7
 
题意:有一个图, ‘.’表示可以通行,'#'表示不能通行,然后给出 m 个管道的起始点和结束点
你可以从任意一个管道开始,要走过每一条管道,问最短时间花费为多少。
在管道里走无花费,在路面上走一格花费为一,只能上下左右走
思路:既然要遍历所有管道,且花费最少,那么就先得在管道之间建立起最短距离
于是我们枚举每个管道的结束为止,然后去枚举其他管道的开始为止,用 bfs 获取之间的最短距离,然后存下这个距离,构成一个新的图。
整幅图 最大不超过 15 * 15 ,管道最多也就是 15 个,可以用状态压缩动态规划就是状压DP写
dp[i][j] 中 i 表示当前站立的状态, j 表示当前所站的管道编号, 所以 i 范围是 0 到 1 << m , j范围是 0 到 m
(ps:i 是表示一个状态  例如 :i = 5 = 101 (2进制) 表示 第一和第三个位置已经访问过了)
然后给所有状态赋值为 inf ,但是 dp[1 << i][i] 需要赋值为 0 ,因为这表示当前站的点为起始点
所以最大状态就是 1 << m ,枚举 1 到 (1 << m) 的状态,在状态下枚举每个已访问过的点 j
(i & (1 << j)) = 0 就表示这个点在这个状态下是没访问过的,那就跳过,否则就枚举第二个点 k
(i & (1 << k)) = 0  表示 k 也没有访问过,也跳过,或者 j = k 也跳过,然后就是更新最小值了
从 k 去到 j ,在 i 状态下是 j k 都有访问过的,那么 i ^ (1 << j) 就表示 j 没有被访问过的状态,但是访问了 j 就到达当前 i 状态了,所以这里就可以表示 从 k 到 j ,看 k 到 j 的话值能不能更新小,就如此枚举完所有其他点,就能得到当前状态的最小值了。
那么要遍历完所有管道,就是  111111111111的情况, 就是枚举所有的 j使得 i & (1 << j) = 1的时候,就表示每个点都有访问过,然后去 dp[i] 下找最小的值就可以了!
#include<cstdio>
#include<iostream>
#include<queue>
#include<algorithm>
#include<cstring>
using namespace std;
#define inf 0x3f3f3f
#define maxn 20
#define mem(a,x) memset(a,x,sizeof(a))
const int dir[4][2] = {1,0,-1,0,0,1,0,-1};
char mapp[maxn][maxn];
int n,m;
struct node1{
	int x1,y1,x2,y2;
}a[maxn];
struct node2{
	int x,y,dis;
};
int b[maxn][maxn][maxn][maxn];
int ex,ey;
bool vis[maxn][maxn];
int dp[33000][maxn];
int bfs(int sx,int sy){
	mem(vis,false);
	queue<node2>q;
	node2 s,e;
	s.x = sx;
	s.y = sy;
	s.dis = 0;
	q.push(s);
	vis[sx][sy] = true;
	while(q.size()){
		s = q.front();
		q.pop();
		if(s.x == ex && s.y == ey){
			return s.dis;
		}
		for(int i = 0;i < 4;i++){
			int mx = s.x + dir[i][0];
			int my = s.y + dir[i][1];
			if(mx < 1 || mx > n || my < 1 || my > n || vis[mx][my] || mapp[mx][my] == '#')
				continue;
			vis[mx][my] = true;
			e.x = mx;
			e.y = my;
			e.dis = s.dis + 1;
			q.push(e);
		}
	}	
	return inf;
}
int main(){
	while(scanf("%d %d",&n,&m) != EOF){
		for(int i = 1;i <= n;i++)
			scanf("%s",mapp[i] + 1);
		for(int i = 0;i < m;i++){
			scanf("%d %d %d %d",&a[i].x1,&a[i].y1,&a[i].x2,&a[i].y2);
		}
		mem(b,inf);
		for(int i = 0;i < m;i++){
			for(int j = 0;j < m;j++){
				if(i == j)
					continue;
				ex = a[j].x1,ey = a[j].y1;
				int dis = bfs(a[i].x2,a[i].y2);
				b[a[i].x2][a[i].y2][a[j].x1][a[j].y1] = dis;
			}
		}
		int N = 1 << m;
		for(int i = 0;i < N;i++){
			for(int j = 0;j < m;j++)
				dp[i][j] = inf;
		}
		for(int i = 0;i < m;i++) 
			dp[(1 << i)][i] = 0; // 起始点 
		int ans = inf;
		for(int i = 1;i < N;i++){
			int flag = 1;
			for(int j = 0;j < m;j++){
				if(!(i & (1 << j))){ 
					flag = 0;// 每个管道都去过了才去计算 ans 值 
					continue;
				}
				for(int k = 0;k < m;k++){
					if(!(i & (1 << k)) || j == k) // 第k个管道已去过则取值 
						continue;
					dp[i][j] = min(dp[i][j],dp[i ^ (1 << j)][k] + b[a[k].x2][a[k].y2][a[j].x1][a[j].y1]);
					// 到达 j 点的时候,是 状态 i, 从前一个状态算,找到到达 j 且状态为 i 时的最短路 
				}
			}
			if(flag){
				for(int j = 0;j < m;j++)
					ans = min(ans,dp[i][j]);
			}
		}
		if(ans == inf)
			puts("-1");
		else
			printf("%d\n",ans);
	}
	return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值