P5507 机关

文章介绍了使用A*算法解决M星旋钮解锁问题的过程,通过预估步数和优化搜索策略,降低了计算复杂度,实现最少旋转次数解锁机关。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目背景

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

题目描述

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

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

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

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

这个任务就交给你了

输入格式

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

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

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

输出格式

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

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

数据保证有解

输入输出样例

输入 

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
输出 
6
1 2 3 4 5 6

这道题一看就肯定用搜索来写,先说一下普通搜索的思路,这题第一个想到的肯定是广搜,用广搜找每一步,枚举12个按钮的可能性,中途用一个数组,记录路径,找到终点(全都为1)时,输出步数与路径

下面放一段广搜思路的代码:

#include<bits/stdc++.h>
#define int long long
using namespace std;
struct node{
	int x[13];//现在的状态
	int path[18]; //保存路径 
	int step=0;
}n;
int a[13][6];
map<long long,int> vis;
queue<node> q;
int num(int x[]){
	int res=0;
	for(int i=1;i<=12;i++)res=res*10+x[i];
	return res;
}
void bfs(){
	//起点入队
	q.push(n);
	vis[num(n.x)]=1; 
	while(q.size()){
		node t=n=q.front();
		q.pop();
		//可能转动1-12号按钮 
		for(int i=1;i<=12;i++){
			//获取转动i号按钮后的状态
			n.x[ a[i][n.x[i]]]++;
			if(n.x[ a[i][n.x[i] ] ]==5)n.x[ a[i][n.x[i] ] ]=1;
			n.x[i]++;
			if(n.x[i]==5)n.x[i]=1;
			int b=num(n.x);
			if(vis[b]==1){
				n=t;
				continue;
			}
			vis[b]=1;
			n.path[n.step++]=i;
			if(b==111111111111){//终点判断
				printf("%lld\n",n.step);
				for(int j=0;j<n.step;j++)printf("%lld ",n.path[j]);
				return;
			}
			q.push(n);
			n=t;
		}
	}
}
signed main(){
	for(int i=1;i<=12;i++)
		for(int j=0;j<5;j++)
			scanf("%lld",&a[i][j]);
	for(int i=1;i<=12;i++)n.x[i]=a[i][0];
	bfs();
}

交上去我们就会发现只AC了四个,其他全都TLE了。

那么这个时候,我们就要对代码进行优化。

双向广搜我不太推荐,因为要输出路径,双向广搜输出比较麻烦,所以这里呢就改成A*吧 

A*算法就是在所有的节点中选择最优的(最容易到达终点的),这样可以节省许多时间,就好比找东西,先去找可能性更大的,没找到再去找可能性更小的,因此需要一个估价函数,来预估现在的价值,价值更小的也就是我们的优先搜索对象了。

这里我们的价值,就是到终点距离(这道题就是步数)加上已走的步数,估价函数所估计的,就是还要的步数。

好,回归正题,所以就要用优先队列来存储值,给优先队列写一个排序规则,按照val(价值)升序排列,每次照样取队头就行了。

但是,这个估价函数不能偷懒,如果你只返回有几个不一样的值,就会发现,还有一个TLE死犟死犟的,一直A不了

这里的估价函数,得写成每个点距离1还要几步。

完整代码如下:

#include<bits/stdc++.h>
#define int long long
using namespace std;
int h(int x[]){//求出预估值
	int cnt=0;
	for(int i=1;i<=12;i++){
		int ans=x[i];
		int s=0;
		while(ans!=1){
			ans++;
			s++;
			if(ans==5){
				ans=1;
			}
		}
		cnt+=s;
	}
	return cnt;
}
struct node{
	int x[13];//用来存当前状态
	int path[18]; //保存路径 
	int step=0;//记录步数
	int val=0;//存预估值
	friend bool operator <(node a,node b){//优先队列的排序规则
		return a.val>b.val;
	}
}n;
int a[13][6];//用于输入和带动
map<int,int>vis;//vis用来标记
priority_queue<node> q;//创建优先队列
int num(int x[]){
	int res=0;
	for(int i=1;i<=12;i++)res=res*10+x[i];//数组转化为数字
	return res;
}
void bfs(){
	//起点入队
	n.val=h(n.x)+0;//求起点的预估值
	q.push(n);//起点入队
	vis[num(n.x)]=1;//记录
	while(!q.empty()){
		node t=n=q.top();//获取队头
		q.pop();//队头出队
		//可能转动1-12号按钮 
		for(int i=1;i<=12;i++){//循环,遍历每一个按钮
			//获取转动i号按钮后的状态
			n.x[ a[i][n.x[i]]]++;//被联动的按钮做改变
			if(n.x[ a[i][n.x[i] ] ]==5)n.x[ a[i][n.x[i]] ]=1;//边界判断
			n.x[i]++;//当前按钮做改变
			if(n.x[i]==5)n.x[i]=1;//判断边界
			int b=num(n.x);//转化为数字
			if(vis[b]==1){//如果这个状态找过,直接跳过
				n=t;//返回原来状态
				continue;//跳过这次循环
			}
			vis[b]=1;//标记当前状态
			n.val=h(n.x)+n.step;//记录当前预估值
			n.path[n.step++]=i;//路径记录数组记录路径
			if(b==111111111111){//如果到达了终点,输出
				printf("%lld\n",n.step);//输出次数
				for(int j=0;j<n.step;j++)printf("%lld ",n.path[j]);//输出方法
				return;
			}
			q.push(n);//将当前状态入队
			n=t;//回到原来的值
		}
	}
}
signed main(){
	//输入
	for(int i=1;i<=12;i++)
		for(int j=0;j<5;j++)
			scanf("%lld",&a[i][j]);
	//初始化起点状态:333333111111 
	
	for(int i=1;i<=12;i++)n.x[i]=a[i][0];//取每个按钮的当前状态
	bfs();//调用函数
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值