P5507机关-题解
这是道很好的搜索练手题
可以用广为人知的搜索三巨头求解
- 双向搜索
- IDA* (
在这题貌似被禁了) - A*
这篇题解主要介绍用 A_star 算法 AC 这道题
1. 思路
用 结构体 记录 路径 状态
以及实际步数 g(n) 和估价步数 h(n)
我们都知道 A* 算法的核心在于其估价函数 h(n)
每次取出对应 g(n) + h(n) 最小的 进行下一步搜素
引出一个疑惑 :
每改变一个机关的状态都会有另一个机关跟着改变
那么估价函数如何求解 ?
看作是两个一起变换
可以 用当前状态距离目标状态的理想差值除去 2 得到
2.解法
使用优先队列存储结构体
所以我们需要 重载运算符 :
bool operator<(const node &a,const node &b){
return a.g+a.h>b.g+b.h;
}
经检验 :
由于看作两个机关同时向着目标前进 过于理想
此题的估价函数可以稍微调高一些,用于优化时间复杂度
参数为原估价函数的 1.3 倍
但此时的时间复杂度仍高了些
所以最后加上 map 去重 即可
3. Code
#include<bits/stdc++.h>
using namespace std;
struct node{
string state;//状态
string path;//路径
int g;//实际步数
int h;//估价函数
};
map<string,bool> mark;
//重载运算符
bool operator<(const node &a,const node &b){
return a.g+a.h>b.g+b.h;
}
priority_queue<node> q;
//估价函数
int eva(string s){
int cnt=0;
for(int i=1;i<=12;i++)
if(s[i]!='1') cnt+=5-(int)(s[i]-48);
return cnt/2*1.3;
}
void print(node s){
printf("%d\n",s.g);
for(int i=0;i<s.path.size();i++) cout<<(int)(s.path[i]-48)<<" ";
}
int main(){
int gear[13][5];//机关触发机制
node start;
start.state+=(char)(48);
for(int i=1;i<=12;i++)
for(int j=1;j<=5;j++){
int x;scanf("%d",&x);
if(j==1) start.state+=(char)(x+48);
else gear[i][j-1]=x;
}
start.g=0;
start.h=eva(start.state);
if(!start.h){printf("0");return 0;}
mark[start.state]=true;
q.push(start);
while(!q.empty()){
node a=q.top();q.pop();
for(int i=1;i<=12;i++){
//触发机关
int x=(int)(a.state[i]-48);//x为i机关的状态
int j=gear[i][x];//j为i机关的连锁机关
int y=(int)(a.state[j]-48);//y为j机关的状态
//改变状态
if(x==4) x=1;
else x+=1;
if(y==4) y=1;
else y+=1;
node s;//转换入队列
s.state+=(char)(48);
for(int u=1;u<=12;u++){
if(u==i){s.state+=(char)(x+48);continue;}
if(u==j){s.state+=(char)(y+48);continue;}
s.state+=a.state[u];
}
if(!mark[s.state]){//map去重
s.g=a.g+1;
s.h=eva(s.state);
mark[s.state]=true;
s.path=a.path+(char)(i+48);
if(!s.h){print(s);return 0;}
q.push(s);
}
}
}
return 0;
}