HDU 3681 Prison Break(状压DP + 二分)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3681

 

题意:给一张N * M的图,只能上下左右移动,每移动一格消耗一点能量,其中F是起点,D不能走,G点可以把能量加满,Y点必须经过,问最少需要多大的能量容量可以将Y点全部遍历

 

思路:先将原图hash成易处理的图,再bfs预处理出每个F,G,Y到其他点的最短距离,然后二分答案进行状压DP,其中dp[s][u]代表在当前状态s下且到达u点时还剩下的能量

 

 

#include <cstdio>
#include <cstring>
#include <queue>
#include <stack>
#include <functional>
#include <utility>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <set>
#include <cmath>
#include <cstdlib>
#include <climits>

#define lson l, mid, rt << 1
#define rson mid + 1, r, rt << 1 | 1
#pragma comment (linker, "/STACK:1024000000,1024000000")

using namespace std;

typedef long long ll;
typedef pair<int, int> P;

const int maxn = 300;
const int inf = 0x3f3f3f3f;

int cnt = 0, head[maxn];

struct edge
{
	int from, to, nxt;
} e[maxn * maxn];

void init()
{
	cnt = 0;
	memset(head, -1, sizeof(head));
}

void addedge(int u, int v)
{
	e[cnt].from = u;
	e[cnt].to = v;
	e[cnt].nxt = head[u];
	head[u] = cnt++;
}

int n, m, tot, st;
int dis[20][20], id[20][20], dx[20], dy[20];
char s[20][20];

bool vis[maxn];
int dp[1 << 16][16];

void bfs(int s)
{
	memset(vis, 0, sizeof(vis));
	queue <P> que;

	P cur, next;
	cur.first = s, cur.second = 0;
	int sx = s / m, sy = s % m;

	que.push(cur);
	vis[s] = 1;
	while (!que.empty())
	{
		cur = que.front();
		que.pop();

		int u = cur.first, su = cur.second;
		int ux = u / m, uy = u % m;

		if (id[ux][uy] != -1)
			dis[id[sx][sy]][id[ux][uy]] = su;

		for (int i = head[u]; ~i; i = e[i].nxt)
		{
			int v = e[i].to;
			if (!vis[v])
			{
				vis[v] = 1;
				next.first = v, next.second = su + 1;
				que.push(next);
			}
		}
	}
}

bool solve(int mid)
{
	memset(dp, -1, sizeof(dp));

	dp[1][0] = mid;
	for (int i = 0; i < (1 << tot); i++)
	{
		for (int j = 0; j < tot; j++)
		{
			if (dp[i][j] == -1 || !(i & (1 << j))) continue;

			if ((st & i) == st) return true;
			for (int k = 0; k < tot; k++)
			{
				if (i & (1 << k)) continue;
				if (dis[j][k] == -1) continue;

				if (dp[i][j] >= dis[j][k])
				{
					if (dp[i | (1 << k)][k] == -1 || dp[i | (1 << k)][k] < dp[i][j] - dis[j][k])
						dp[i | (1 << k)][k] = dp[i][j] - dis[j][k];
					if (s[dx[k]][dy[k]] == 'G')
						dp[i | (1 << k)][k] = mid;
				}
			}
		}
	}

	return false;
}

int nx[4] = {1, 0, -1, 0};
int ny[4] = {0, 1, 0, -1};

int main()
{
	while (~scanf("%d%d", &n, &m) && (n + m))
	{
		init();

		for (int i = 0; i < n; i++)
			scanf("%s", s[i]);

		st = 0, tot = 1;
		memset(id, -1, sizeof(id));
		for (int i = 0; i < n; i++)
		{
			for (int j = 0; j < m; j++)
			{
				char c = s[i][j];
				if (c == 'D') continue;

				if (c == 'F')
				{
					dx[0] = i, dy[0] = j;
					id[i][j] = 0;
				}
				else if (c == 'G')
				{
					dx[tot] = i, dy[tot] = j;
					id[i][j] = tot++;
				}
				else if (c == 'Y')
				{
					dx[tot] = i, dy[tot] = j;
					id[i][j] = tot++;
					st |= (1 << (tot - 1));
				}

				for (int k = 0; k < 4; k++)
				{
					int xx = i + nx[k], yy = j + ny[k];
					if (xx < 0 || yy < 0 || xx >= n || yy >= m || s[xx][yy] == 'D') continue;

					addedge(i * m + j, xx * m + yy);
				}
			}
		}

		memset(dis, -1, sizeof(dis));
		for (int i = 0; i < tot; i++)
			bfs(dx[i] * m + dy[i]);

		// for (int i = 0; i < tot; i++)
		// {
		// 	for (int j = 0; j < tot; j++)
		// 		printf("%d ", dis[i][j]);
		// 	printf("\n");
		// }
		
		int l = 0, r = n * m * (tot - 1), ans = inf;
		while (l <= r)
		{
			int mid = (l + r) >> 1;

			if (solve(mid))
				r = mid - 1, ans = min(ans, mid);
			else
				l = mid + 1;
		}

		if (ans == inf) ans = -1;
		cout << ans << endl;
	}
	return 0;
}



 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值