启发性搜索剪枝

我们先来看一下题目

这道题目本菜第一个想到的是暴搜,但是想了一下,不行,因为时间复杂度太高了,但是还是得试一下,然后在这基础上加一个代价计算函数就可以了,在说暴力后,我会介绍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]

这里的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 << ' ';

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值