JZOJ 6493. 【GDOI2020模拟03.04】迷宫(状压DP)

JZOJ 6493. 【GDOI2020模拟03.04】迷宫

题解

  • 这道题的题面比较玄学,乍一看还以为是道PJ难度的最短路。。。
  • 其实,题目的设定十分有趣,你被放进了迷宫里,只能分清门(A/B/C/D),而不能分清房间(0除外),也就是说,你只知道当前可能所在的房间集合,和整个迷宫的结构,而并不知道具体在哪个房间,不过可以推算每一步可能所在的房间集合,希望最少的步数能保证走出这个迷宫(走出0号房间),
  • 直接设 d i s [ s ] dis[s] dis[s]为当前可能在的房间集合为 s s s的最终答案,然后进行转移。
  • 一种很自然的想法,每个状态 s s s直接由四个状态转移过来,分别是走四扇门出去到达的房间集合,
  • d i s [ s ] = m i n ( d i s [ s 1 ] , d i s [ s 2 ] , d i s [ s 3 ] , d i s [ s 4 ] ) + 1 dis[s]=min(dis[s1],dis[s2],dis[s3],dis[s4])+1 dis[s]=min(dis[s1],dis[s2],dis[s3],dis[s4])+1
  • 然而仔细推敲过后,发现这样并不是正确的(答案可能更优)
  • 以下是重点内容
  • 我们不仅可以通过当前的房间集合和选择走出的门判断走到的房间集合,也可以通过走出后到达的门得出走到的房间集合,
  • 简单地说,就是原本的四种转移(A/B/C/D)变成了十六种(A->A/A->B/A->C…D->D,分别为走出的门和走进的另一扇门),
  • 不过,也不是直接在这十六种状态中取 m i n min min更新,因为走进的另一扇门是在走出前不能确认的吗,也就是说,选择好了特定的一扇门走出后,才能通过走进的门得到当前可能在的四个集合,这四个是相互独立的,但都是在选择好了走出的门的前提下的,
  • 因此还是要枚举选哪个门走出去,在走出去后的四种情况中取 m a x max max(注意,是 m a x max max,作为转移的值,去更新 d i s [ s ] dis[s] dis[s]
  • 具体实现需要分层DP,写的不好可能会被卡。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 1050000
int st[25][4][2];
int a[25],g[N][4][4];
int last[N],nxt[N*16],to[N*16][4],len=0;
int dis[N],p[N],q[N],e[N],b[21][N];
void add(int x,int A,int B,int C,int y)
{
	to[++len][0]=y;
	to[len][1]=A,to[len][2]=B,to[len][3]=C;
	nxt[len]=last[x];
	last[x]=len;
}
int main()
{
	int n,Q,i,j,k,x,y;
	scanf("%d%d",&n,&Q);
	for(i=1;i<=2*n;i++)
	{
		char c,d;
		scanf("%d %c %d %c",&x,&c,&y,&d);
		st[x][c-'A'][0]=y,st[x][c-'A'][1]=d-'A';
		st[y][d-'A'][0]=x,st[y][d-'A'][1]=c-'A';
	}
	st[0][0][0]=st[0][1][0]=st[0][2][0]=st[0][3][0]=0;
	st[0][0][1]=0,st[0][1][1]=1,st[0][2][1]=2,st[0][3][1]=3;
	for(i=0;i<4;i++)
		for(j=0;j<4;j++) g[1][i][j]=1;
	for(i=2;i<(1<<n);i++)
	{
		int t=i,s=0;
		while(t%2==0) t/=2,s++;
		for(j=0;j<4;j++)
			for(k=0;k<4;k++) g[i][j][k]=g[i-(1<<s)][j][k];
		for(j=0;j<4;j++)
			g[i][j][st[s][j][1]]|=1<<st[s][j][0];
	}
	e[0]=0;
	for(i=1;i<=1<<n;i++) if(i%2) e[i]=e[i/2]+1; else e[i]=e[i/2];
	for(i=1;i<1<<n;i++) b[e[i]][++b[e[i]][0]]=i;
	memset(dis,127,sizeof(dis));
	dis[0]=0,dis[1]=1;
	
	for(k=1;k<=n;k++)
	{
		for(i=2;i<=(1<<n);i++) 
		{
			if(dis[i]<=k) continue;
			dis[i]=min(dis[i],max(max(dis[g[i][0][0]],dis[g[i][0][1]]),max(dis[g[i][0][2]],dis[g[i][0][3]]))+1);
			dis[i]=min(dis[i],max(max(dis[g[i][1][0]],dis[g[i][1][1]]),max(dis[g[i][1][2]],dis[g[i][1][3]]))+1);
			dis[i]=min(dis[i],max(max(dis[g[i][2][0]],dis[g[i][2][1]]),max(dis[g[i][2][2]],dis[g[i][2][3]]))+1);
			dis[i]=min(dis[i],max(max(dis[g[i][3][0]],dis[g[i][3][1]]),max(dis[g[i][3][2]],dis[g[i][3][3]]))+1);
		}
	}
	while(Q--)
	{
		scanf("%d",&x);
		printf("%d\n",dis[x]);
	}
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值