luogu P5507 机关

原题链接:


https://www.luogu.com.cn/problem/P5507icon-default.png?t=LA92https://www.luogu.com.cn/problem/P5507

 展开

题目背景

Steve成功降落后,在M星上发现了一扇大门,但是这扇大门是锁着的

题目描述

这扇门上有一个机关,上面一共有12个旋钮,每个旋钮有4个状态,将旋钮的状态用数字1到4表示

每个旋钮只能向一个方向旋转(状态:1->2->3->4->1),在旋转时,会引起另一个旋钮也旋转一次(方向相同,不会引起连锁反应),同一旋钮在不同状态下,可能会引起不同的旋钮旋转(在输入中给出)

当所有旋钮都旋转到状态1时,机关就打开了

由于旋钮年久失修,旋转一次很困难,而且时间很紧迫,因此Steve希望用最少的旋转次数打开机关

这个任务就交给你了

输入格式

12行,每行5个整数,描述机关的状态

第i行第一个整数si​表示第i个旋钮的初始状态是si​

接下来4个整数a{i,j},j=1,2,3,4表示这个旋钮在状态jj时旋转,会引起第a{i,j}​个旋钮旋转到下一个状态

输出格式

第一行一个整数n,表示最少的步数

第二行n个整数,表示依次旋转的旋钮编号

数据保证有解

输入输出样例

输入 #1复制

3 3 7 2 6
3 1 4 5 3
3 1 2 6 4
3 1 10 3 5
3 2 8 3 6
3 7 9 2 1
1 1 2 3 4
1 3 11 10 12
1 8 6 7 4
1 9 9 8 8
1 12 10 12 12
1 7 8 9 10

输出 #1复制

6
1 2 3 4 5 6

输入 #2复制

3 3 7 2 6
3 1 4 5 3
3 1 2 6 4
3 1 10 3 5
3 2 8 3 6
3 7 9 2 1
1 1 2 3 4
1 3 11 10 12
1 8 6 7 4
1 9 9 8 8
1 12 10 12 12
1 7 8 9 10

输出 #2复制

6
1 1 2 3 4 5

输入 #3复制

4 2 2 2 2
4 1 1 1 1
1 1 1 1 1
1 1 1 1 1
1 1 1 1 1
1 1 1 1 1
1 1 1 1 1
1 1 1 1 1
1 1 1 1 1
1 1 1 1 1
1 1 1 1 1
1 1 1 1 1

输出 #3复制

1
1

输入 #4复制

4 9 3 4 5 
1 9 8 12 11 
4 7 5 6 12 
3 2 2 11 2 
3 6 8 2 12 
4 8 4 2 11 
2 12 9 5 3 
4 1 1 11 1 
1 1 7 4 1 
4 11 6 12 8 
2 6 3 7 6 
4 3 9 7 10 

输出 #4复制

10
11 4 6 10 7 7 5 9 9 9 

说明/提示

样例1和2输入相同,两个输出都可以通过

样例3解释:

414334 241424
旋转11到状态3,引起3旋转到状态1
411334 241434
旋转4到状态4,引起11旋转到状态4
411434 241444
旋转6到状态1,引起11旋转到状态1
411431 241414
旋转10到状态1,引起8旋转到状态1
411431 211114
旋转7到状态3,引起9旋转到状态2
411431 312114
旋转7到状态4,引起5旋转到状态4
411441 412114
旋转5到状态1,引起12旋转到状态1
411411 412111
旋转9到状态3,引起7旋转到状态1
411411 113111
旋转9到状态4,引起4旋转到状态1
411111 114111
旋转9到状态1,引起1旋转到状态1
111111 111111

数据保证存在打开机关的方式

每个测试点10分

只要你输出格式正确,输出了正确的步数,并给出了任意一种正确方案,就能得到该测试点的得分

否则,该测试点不得分

数据范围:

测试点所需步数
14
26
38
49
510
611
712
813
915
1017

思路:

        个人认为十分不错的一道题,对于基础算法学习者的重要难点算法(状态压缩以及双向bfs)都进行了较不错的考察,启发式搜索我不会,这里使用双向bfs

        那么他有四个状态如何进行状态压缩呢?我们可以将其4个状态用两个二进制位来存储。0表示第一个状态,1表示第二个状态,2表示第三个状态。第i个旋钮的状态表示为state>>(i<<1)其为0时表示第一个状态,为了方便我们将旋钮和状态从编号0开始存储。

        在搜索的过程中如何进行状态转移?

        假设该旋钮当前状态是s,将其顺时针扭向下一个状态时可以将其(s+1)&3,可以自行模拟一下。而逆时针为(s+3)&3更改了当前旋钮的状态之后我们怎么将其放入我们当前所有旋钮的状态state里呢?可以知道(s<<(i<<1))就是将第i个旋钮状态s放在第i个旋钮的位置上了再将其^(((s+1)&3)<<(i<<1))再^上state就可以将状态成功转移了,看着比较难懂,其实你模拟一下做个过程,很容易明白

#include<bits/stdc++.h>
using namespace std;
inline int read(){
    int sum = 0,fu = 1;char ch = getchar();
    while(!isdigit(ch)) { if(ch == '-') fu = -1;ch = getchar();}
    while(isdigit(ch)) { sum = (sum<<1)+(sum<<3)+(ch^48);ch =getchar();} return sum * fu;
}
int v[1<<25];//表示当前状态是顺时针还是逆时针旋转 
int vis[1<<25];//表示当前状态是否搜到过 
int fa[1<<25];//表示当前状态由哪个状态转移 
int a[1<<25],b[1<<25];//a表示正向搜索路径,b表示反向搜索路径 
int cc[1<<25];//表示转移到当前状态旋转了哪个旋钮 
int nxt[20][6];
int m1,m2,mm;
int main()
{
	int start = 0;
	for(int i = 0;i<=11;++i)
	{
		int x = read();
		start |= (x-1)<<(i<<1);//因为用2个二进制为表示 所以i要乘2才是他在二进制位的位置 
		for(int j = 0;j<=3;++j)
			nxt[i][j] = read()-1;
	}
	queue<int> q;
	v[start] = 1;//1为正向搜索,2为反向搜索 
	v[0] = 2;
	q.push(start);
	q.push(0);
	vis[start] = vis[0] = 1;
	bool flag = 0;
	while(!q.empty()&&!flag)
	{
		int state = q.front();
		int dirc = v[state];
		q.pop();
		for(int i = 0;i<=11;++i)
		{
			int nextstate;
			if(dirc == 1)
			{
				int s = (state>>(i<<1))&3;//将当前要旋转的旋钮状态找到 
				int nx = nxt[i][s];//找到将要被影响的旋钮 
				int nxs = (state>>(nx<<1)&3);//找到被影响旋钮的状态 
				nextstate = state^(s<<(i<<1))^(((s+1)&3)<<(i<<1));//先旋转第一个旋钮 
				nextstate^=(nxs<<(nx<<1))^(((nxs+1)&3)<<(nx<<1));//再旋转被影响的旋钮 
			}
			else//同上 
			{
				int s = (state>>(i<<1))&3;
				int nx = nxt[i][(s+3)&3];
				int nxs = (state>>(nx<<1)&3);
				nextstate = state^(s<<(i<<1))^(((s+3)&3)<<(i<<1));
				nextstate ^= (nxs<<(nx<<1))^(((nxs+3)&3)<<(nx<<1));
			}
			if(vis[nextstate])//判断是否搜到过这个状态 
			{
				if(v[nextstate] == dirc)	continue;//如果搜到过且方向相同则跳过
				//如果方向相反那就是相遇了找到了
				//使用m1表示正向搜到的最后一个状态,m2表示反向搜到的最后一个状态,mm表示他们中继选择的旋钮 
				m1 = dirc==1?state:nextstate;
				mm = i+1;
				m2 = dirc==1?nextstate:state;
				flag = true;break;
			}
			fa[nextstate] = state;//表示这个状态是由上个状态转移而来 
			v[nextstate] = dirc;
			vis[nextstate] = 1;
			cc[nextstate] = i+1;//表示当前状态是选择哪个旋钮转移的 
			q.push(nextstate);
		}
	}
	int cnt1 = 0,cnt2 = 0;
	int state = m1;
	while(state!=start)//回溯找当时选择了什么点 
	{
		a[++cnt1] = cc[state];
		state = fa[state];
	}
	state = m2;
	while(state!=0)//同上 
	{
		b[++cnt2] = cc[state];
		state = fa[state];
	}
	printf("%d\n",cnt1+cnt2+1);//+1是因为中继选择的点也要加上 
	for(int i = cnt1;i;--i)
		printf("%d ",a[i]);
	printf("%d ",mm);
	for(int i = 1;i<=cnt2;++i)
		printf("%d ",b[i]);
	return 0;
 } 

       

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值