HDU 4114 Disney's FastPass

传送门

状压dp+最短路。
一个无向图,接下来给出一些兴趣点,每个兴趣点被访问时有一定耗时。找一条从起点开始访问所有兴趣点再回到起点耗时最短的路(必须访问所有兴趣点,途中可以经过任意点(经过任意点包括兴趣点都不耗时),全程总耗时包括访问兴趣点和途经所有边)。每个兴趣点在一些点有通票,经过这些点中任意一个就可以拿到这个兴趣点的通票,拿到通票之后意味着访问这个兴趣点的耗时可以缩短。

题意比较复杂,给定的兴趣点的最大值是8。我们可以考虑用二进制来表示当前访问过的兴趣点以及当前拿到哪些兴趣点的通票,所以这两维状态的数量都是2^8=256种。每个点含有的通票都是来自兴趣点的,所以也用同样的状态表示方法来表示每个点含有哪些兴趣点的通票(在输入时完成)。

所谓状态转移,一个是当前状态可转移到哪些状态,另一个是可转移的状态的值怎么用当前状态的值来表示。dp[k][i][j]表示当前从起点走到k点(已获取k点含有的通票),拿到的通票的二进制状态的十进制表示为i,访问过的兴趣点的二进制状态的十进制表示为j,在此情况下的最短耗时。

可转移三种状态,一种是访问状态(转移到当前还没有访问过的兴趣点),一种是经过状态(转移到所有点(包括所有兴趣点)),一种是答案状态(如果当前状态已访问过所有兴趣点)。而对于新状态的值,从当前点到新点肯定要走最短路,需要预先floyd一遍。

因为访问兴趣点需要耗时,所以每个兴趣点只访问一次就好了(这与下句话并不冲突)。题目中说可能有多个兴趣点是同一个点,那么就要访问这个点多次,每次都当成一个兴趣点。

最后要注意的问题就是dp数组每一维的循环次序,可以把i,j当成同一组,对k的循环一定要在i,j的下层进行。为什么?因为当前状态完全可以转移到k更小的状态,但是转移到的状态的i,j的值一定不会减小。


#include <cstdio>
#include <iostream>
#include <algorithm>
#include <vector>
#include <cstring>
#include <string>
#include <queue>
using namespace std;                             // 传说中的状压dp

const int INF = 0x3F3F3F3F;
const int MAXN = 51;
int N, M, K, X;
int g[MAXN][MAXN];

int pos[8], T[8], FT[8];
int pass[MAXN];

int dp[MAXN][1 << 8][1 << 8];
int ans;

void init()
{
	memset(g, 0x3F, sizeof g);
	memset(pass, 0, sizeof pass);
	memset(dp, 0x3F, sizeof dp);
	for (int i = 1; i <= N; i++) g[i][i] = 0;
	ans = INF;
}

void floyd()
{
	for (int k = 1; k <= N; k++)
	{
		for (int i = 1; i <= N; i++)
		{
			for (int j = i + 1; j <= N; j++)
			{
				if (g[i][k] != INF && g[k][j] != INF && g[i][k] + g[k][j] < g[i][j])
				{
					g[i][j] = g[j][i] = g[i][k] + g[k][j];
				}
			}
		}
	}
}

void run()
{
	dp[1][0][0] = 0;                                                   // 初始状态

	for (int i = 0; i < (1 << K); i++)
	{
		for (int j = 0; j < (1 << K); j++)
		{
			for (int k = 1; k <= N; k++)
			{
				int t = dp[k][i][j];
				if (t != INF)
				{
					if (j == ((1 << K) - 1))
					{
						ans = min(ans, t + g[k][1]);
						continue;
					}
					for (int n = 0; n < K; n++)                        // 1
					{
						if (!(j&(1 << n)) && g[k][pos[n]] != INF)      // 不加也行
						{
							int& u = dp[pos[n]][i | pass[pos[n]]][j | (1 << n)];
							u = min(u, t + g[k][pos[n]] + (i&(1 << n) ? FT[n] : T[n]));
						}
					}
					for (int n = 1; n <= N; n++)                       // 2  1,2部分可以调换位置,无影响
					{
						if (g[k][n] != INF)                            // 不加也行
						{
							int& u = dp[n][i | pass[n]][j];
							u = min(u, t + g[k][n]);
						}
					}
				}
			}
		}
	}
}

int main()
{
	int a, b, c;
	scanf("%d", &X);
	for (int i = 1; i <= X; i++)
	{
		scanf("%d%d%d", &N, &M, &K);
		init();
		for (int j = 0; j < M; j++)
		{
			scanf("%d%d%d", &a, &b, &c);
			g[a][b] = g[b][a] = c;
		}
		for (int j = 0; j < K; j++)
		{
			scanf("%d%d%d%d", &pos[j], &T[j], &FT[j], &a);
			for (; a--;)
			{
				scanf("%d", &b);
				pass[b] |= 1 << j;
			}
		}
		floyd();
		run();
		printf("Case #%d: %d\n", i, ans);
	}

	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值