我们先来看一下题目
这道题目本菜第一个想到的是暴搜,但是想了一下,不行,因为时间复杂度太高了,但是还是得试一下,然后在这基础上加一个代价计算函数就可以了,在说暴力后,我会介绍A*算法;
我们先创建一个结构体,结构体里有以下成员:
1:一个int数组st[N]里面存储着每个开关的状态;
2:一个int cnt,计算从起始状态到目前状态需要多少步;
3:vector<int> path 存储从起始状态到目前状态的路径
4:long long num;用st[N]数组转化过来的身份识别码,每一个状态都有唯一一个识别码;
然后我们还需要一个key值为long long ,value值为bool的unordered_map用来存储标记每个身份码是否出现过;
另外我们还需要一个queue来存储每一个状态,以便于我们可以通过这些状态来扩展其他状态
然后就是暴力遍历了,有以下步骤:
(1)将一个状态改变其1~12个按钮各一次,然后制作新状态mid;
(2)判断mid的身份码之前是否出现过;
1:出现过,则跳过;
2:没出现过:则加人队列,并将其身份标记;
(3)判断这个mid的身份码是否是目标的身份码【这里本菜的目标身份码是111111111111】
1:如果不是,则继续搜索;
2:如果是:退出遍历,输出结果;
但这样的计算过程很是慢,我们可以打个比方:
朴素的bfs就像一个nc,不会辨别,就像你将东西丢家里了,bfs找的方式是从门口一寸一寸找,就算你丢的东西是碗筷,bfs找到厕所里他也不会放弃,所以bfs才如此缓慢;
那么我们为了抑制搜索树的成长,且在这种抑制下我们能得到正确答案,我们可以给bfs注入一点“智力”,即我们给每个状态一个代价变量,代价低的优先遍历,代价高的稍后考虑,这里有一个公式,用来给每个状态评分
这里的f[x]为状态的代价,g[x]为从起始状态到目前状态付出的代价【就是从起始状态到目前状态已经走了多少步】,d[x]为从目前状态到目标状态”估计“出的最小代价【这个代价是可以人为规定的,不同的d[x]定义方式其得到答案的速度不同】,看得出来,这个公式包含着过去、未来、现在这三个元素;
这里本菜目估了一个可以ac的d[x]计算方式,就是单独计算每个按钮到转到1的最小步数,然后相加即可;
另外,我们大量第求一个集合中的最值,这里肯定得用到优先队列,另外我们要自定义优先队列的比较方式,所以我们得构建一个函数cmp;
那么具体代码如下:
#include<iostream>
#include<algorithm>
#include <queue>
#include<vector>
#include <unordered_map>
#define ll long long
using namespace std;
const int N = 13;
unordered_map<ll, bool> ha;//身份码识别神器,unordered_map yyds
struct id {
int st[13];//存储当前按钮的每个状态
ll num = 0;//身份码
ll cont = 0;//从起始状态到目前状态走的步数
double cost = 0;//当前状态的代价
vector<int> path;//从起始状态到当前状态的路径
};
int cha[N][N];//其中cha[i][j]为第i个按纽在状态为j时牵连的按钮编号
ll get(id x) {//这个函数的作用是输入一个状态,然后返回这个状态的身份码
ll res = 0;
for (int i = 1; i <= 12; i++) res = res * 10 + x.st[i];
return res;
}
struct cmp//自定义函数,用来自定义优先队列
{
bool operator()(id a, id b) {
return a.cost > b.cost;
}
};
double get_cost(id t) {//这个函数是获取当前状态的代价
t.cost = 0;
for (int i = 1; i <= 12; i++) {
if (t.st[i] != 1) {
if (t.st[i] == 2) t.cost += 3;
if (t.st[i] == 3) t.cost += 2;
if (t.st[i] == 4) t.cost += 1;
}
}
t.cost += t.cont;
return t.cost;
}
id make(id t, int pos) {//这个函数的作用是给定一个状态和修改的位置,然后返回修改后的状态
int pos1 = cha[pos][t.st[pos]];
t.st[pos] = (t.st[pos] + 1) % 5 + (t.st[pos] + 1 == 5);
t.st[pos1] = (t.st[pos1] + 1) % 5 + (t.st[pos1] + 1 == 5);
t.num = get(t);
t.path.push_back(pos);
t.cont++;
t.cost = get_cost(t);
return t;
}
id bfs(id sta) {
priority_queue<id, vector<id>, cmp> que;
sta.cost = get_cost(sta);
que.push(sta);
while (que.size()) {
id t = que.top();//top()返回代价最小的状态
que.pop();
for (int i = 1; i <= 12; i++) {//改变12个按钮的状态来扩展状态
id mid = make(t, i);
if (mid.num == 111111111111) {
return mid;
}
if (!ha[mid.num]) {//看这个状态之前是否出现过
ha[mid.num] = 1;
que.push(mid);
}
}
}
}
int main() {
id sta;
for (int i = 1; i <= 12; i++) {//接收数据
cin >> sta.st[i];
for (int j = 1; j <= 4; j++) {
cin >> cha[i][j];
}
}
sta.num = get(sta);
id ans = bfs(sta);
cout << ans.cont << endl;
for (auto t : ans.path)
cout << t << ' ';
}