jzoj6493 迷宫 (dp)

题意

给你一个迷宫,有n-1个房间0~n-1。每个房间有4扇门,分别有标号 1234,除了0号点有标识为终点之外,每个点都是一样的。从一扇门进去,会从另外一扇门出来。 你知道这两扇门的标号,保证所有房间是互相可达的。现在给你一个集合S,然后将你放到|S|个房间中的任意一个(但你不知道确切是哪个),求你到达终点的最小的最大步数。
n<=20,对所有子集询问。

分析

  • 首先要分清:这道题不是让你求最短路。而是让你找出一种走法,无论从S个点的哪个点出发都能到终点的最小步数。
  • 有点像博弈一样。你的对手可以决定你的起点,并且希望最大化你的步数,而你可以决定你的走法,希望要最小化你的步数。
  • 注意这里所谓的走法当然不是固定的。就像取石子博弈一样,会随着对手的选择而改变。有不同的地方就是,你只能通过走的时候获取的边信息来得知对手选择的方法,而不是一开始就被告知。
  • 设状态f[S]为当前可能在的房间集合S,你的最优结果。转移方程为
  • f [ S ] = m i n ( f [ S ] , m a x ( f [ S 0 ] , f [ S 1 ] , f [ S 2 ] , f [ S 3 ] ) + 1 ) f[S]=min(f[S],max(f[S0],f[S1],f[S2],f[S3])+1) f[S]=min(f[S],max(f[S0],f[S1],f[S2],f[S3])+1)
  • S0~S3是选择这种方向的四种可能后继。你的对手自然会选择最大的一种可能,而你可以决定当前走的方向。
  • 由于边权是1,所以直接从终点状态开始bfs就好了。注意,除了终点状态之外其他状态不能包含终点,否则是不合法的。

Q: 为什么不会有终点状态到达不了的状态。
A: 对于状态S,由于你知道地图,所以考虑先将其中的一个房间走到终点,再考虑将第二个房间走到终点…最终始终会到终点状态。
由此可见,答案上界是不会超过n^2的。

#include <bits/stdc++.h>
using namespace std;
const int N = 21;
int n, q;
int f[1 << N];
pair<int,int> e[N][4];
vector<pair<int,int> > to[1 << N];
int ful, g[1 << N];
int cnt[1 << N][4];
int main() {
	freopen("maze.in", "r", stdin);
	// freopen("maze.out", "w", stdout);
	cin >> n >> q;
	for(int i = 1; i <= 2 * n; i++) {
		int x, y; char a, b;
		scanf("%d %c %d %c", &x, &a, &y, &b);
		a -= 'A', b -= 'A';
		e[x][a] = make_pair(y, b);
		e[y][b] = make_pair(x, a);
	}
	ful = 1 << n;
	#define lowbit(x) ((x) & -(x))
	for(int i = 1; i < ful; i++) g[i] = g[i - lowbit(i)] + 1;
	for(int i = 2; i < ful; i++) {
		for(int dir = 0; dir < 4; dir++) {
			int s[4]; memset(s,0,sizeof s);
			for(int x = 0; x < n; x++) if (i & (1 << x)) {
				s[e[x][dir].second] |= 1 << e[x][dir].first;
			}
			for(int j = 0; j < 4; j++) if(s[j] != 0) {
				if ((s[j] & 1) && s[j] != 1) s[j]--;
				to[s[j]].push_back(make_pair(i, dir));
				// cout << s[j] << " " << i << " " << dir << endl;
				cnt[i][dir]++;
			}
		}
	}
	memset(f,127 / 2,sizeof f);
	f[1] = 1;
	static int Q[1 << N], h = 0, t = 0;
	Q[++t] = 1;
	while (h < t) {
		int x = Q[++h];
		for(pair<int,int> p : to[x]) {
			int y = p.first, dir = p.second;
			if(--cnt[y][dir] == 0) {
				if (f[y] == f[0]) {	
					f[y] = f[x] + 1;
					Q[++t] = y;
				}
			}
		}
	}
	for(int i = 1; i <= q; i++) {
		int k; scanf("%d", &k);
		if (k & 1) k--;
		if (k == 0) printf("1\n"); else 
		printf("%d\n", f[k]);
	}
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值