先看题:
如果不考虑时间复杂度,那么这道题最好写的那么肯定是bfs;
但是肯定会超时,这时本菜发现,一种味道的糖果有两种状态,一种是被尝过,一种是没尝过。
那么我们可以将M种糖果的状态压缩到一个int型二进制中,然后我们又知道最终我们要找的状态的二进制数对应的十进制数为(1<<m)-1,那么一个算法就对应上了,即A*算法,又称启发性搜索;【至于为什么叫这个算法为玄学算法,dddd】
在这种我简单说一下A*算法吧【之前有写过一篇更加详细的】
A*算法在本菜眼里呢等于优先队列+评估函数+朴素bfs,其实很好理解这个函数的;
我们给每个搜索的状态应该新的元素,即代价,然后我们用优先队列挑出代价最小的那个状态,然后对这个状态就行扩展;
打个比方,如果你在家里丢了东西,那么你一定会去有可能存在失物的地方去找,而不是去鸟不拉屎的杂物间找;
我们的这个评估函数就是给每个状态一个评估值,评估值越小,说明越靠近答案,有以下公式:
f[x]为一个状态的代价,g[x]为从起始状态到此状态用了多少步,h[x]为从目前状态到目标状态预计要付出的代价;
那么有以下代码:
#include<iostream>
#include<algorithm>
#include<queue>
#include<time.h>
#include<math.h>
using namespace std;
const int M = 1 << 20, N = 100;
int n, m, k;
clock_t a, b;
struct p {
bool st1[N] = {};//当前状态用到的袋状态,st1[i]=1表示第i包糖果被选择,=0表示没被选择
int id = 0;//当前状态的id
double cost = 0;//一个状态的代价
int num = 0;//用到的袋数,对应位从起始状态到此状态付出的代价
}st[N + 10];
int ha[M];//记录每个状态是否出现过,防止爆内存
double get_cost(p a) {//这个函数的作用是给出一个状态a,然后返回这个状态的代价
double sum = 0;
for (int i = 0; i < m; i++) {
if (!((a.id >> i) & 1)) sum++;
}
sum /=2.255;
sum += a.num;
return sum;
}
struct cmp {
bool operator() (p a, p b) {
return b.cost < a.cost;
}
};
p make(p a, int x) {//这个函数的作用是给出一个状态a,然后在这个状态的基础上选择第x包糖果,然后返回改变后的状态
a.num++;
a.id = a.id | st[x].id;
a.st1[x] = 1;
a.cost = get_cost(a);
return a;
}
int bfs() {
priority_queue<p, vector<p>, cmp> que;
for (int i = 1; i <= n; i++) {
que.push(st[i]);
}
while (que.size()) {//一个朴素的bfs
p t = que.top();
que.pop();
ha[t.id] = 1;
if (t.id == ((1 << m) - 1))
return t.num;
p mid;
for (int i = 1; i <= n; i++) {
if (!t.st1[i]) {
mid = make(t, i);
if (!ha[mid.id])
que.push(mid), ha[mid.id] = 1;
}
}
}
return -1;
}
int main() {
cin >> n >> m >> k;
for (int i = 1; i <= n; i++) {
for (int j = 0; j < k; j++) {
int x; cin >> x;
st[i].id = st[i].id | (1 << (x - 1));
}
st[i].cost = get_cost(st[i]);
st[i].num = 1;
}
cout << bfs();
}