题解 P2534 【[AHOI2012]铁盘整理】
NKU_AI_HMX · · 题解
写在前面:管理大大审核辛苦了,这篇题解代码吸氧26ms左右。
目录
- 看完本篇题解你能收获什么?
- 对萌新介绍啥是
A^{* } ,IDA^{* } 。 - 怎么想到这个估价函数的?没有其他的了吗?
- 一些其他题解没有的小优化(对这题影响不大)。
正文
1. 看完本篇题解你能收获什么?
如果您和我一样是萌新呢?可以了解什么是
如果您是大佬,过来简单看看代码,建议您直接跳至第四点,或者代码框。
2. 啥是 A^{* } , IDA^{* }
因为关于
相信来大家一定都很熟悉搜索,
其实这就像我们找东西,家里落了一个东西,不知道在哪,我们又不记得上 次放哪了。我们就要找它,我们一定不会先把床移开,看看是不是在床下, 或者首先到你家布满灰尘的储物间去找。我们会先找那些更可能的地方,比 如桌子上?书下面?等等。而你之所以做出这样的选择就是基于我们自己的“判断函数”,只不过这个判断函数不单单和可能性相关,但可能性一定是我们判断的重要因素。
所以
int evaluate()
{
int cnt = 0;
for (int i = 1; i <= n; i++)
{
if (abs(a[i] - a[i + 1]) != 1)cnt++;
}
return cnt;
}
以上是这一题用到的估价函数,这个函数是我们预估从当前状态走到末状态最少需要多少步(注意!我们的估价函数得出的步数一定要小于等于实际最小步数,因为我们如果估计小了,无非多搜索一些点,但是估计大了那就会遗失很多情况,很可能就导致了错误答案。)我们得到函数以后,我们每到一个点就估计当前状态到最终状态还剩的最少步数,如果当前已经走的步数
那么这两个算法难在哪里呢?
答案是:估价函数的确定
这里给大家拓展一下思维,其实大家会发现估价函数的确定是一件很玄乎的事儿,没有一个定式,靠感觉,或者经验,而且对于有些估价函数你并不好确定它和理想估价函数是不是有相同趋势,可以理解为:我们不好判断这个估价函数是不是在所有情况下都很好。而正是它这玄乎的一点让它有了“智能”,因为它可以选择性搜索,而不完全是盲目的了。
人工智能算法里面一个很重要的领域是强化学习,其实我们会发现它的一套体系最开始也是基于一个类似估值函数的东西,只不过它会在每次和环境交互之后根据结果来反向传播,修改原有参数,让这个函数更加贴近理想函数。而慢慢的单一函数的局限性也就成为了人工智能发展的一个瓶颈,所以我们后来就创造了神经网络,我们可以理解其为很多层很多个估值函数的组合,最后我们也会根据环境的反馈反向传播,修改各个神经元的数值,也就是修改估值函数的参数(了解泰勒展开的同学应该知道基本上所有函数都可以用多项式表示,只不过系数不同而已,这也就给了神经网络一个极其复杂的“估值函数”)
3. 怎么想到这个估价函数的?
我们回到这道题,因为这道题的估值函数题解也都是一样的,大家也都讲的比较清楚了(不清楚的话,欢迎评论留言,可以探讨)笔者这里就讲讲笔者的思路。
1.套用其他题思路,直接算当前状态到理想状态的“距离”
这种做法在八数码问题里面确实有用,但是在这里我们需要发现我们每一步的操作会影响很多盘的实际位置,那么我们把每个盘子单独拿出来分析就显得有些困难了,所以我们要转换思路,找到每次状态改变的不变量!盘子之间的相对位置关系。
2.算单调序列的个数
这是我最开始的想法,但是用这个的话必然是会
4. 一些其他题解没有的小优化
1.
if (i == x||abs(a[i+1]-a[i])==1)continue;//这里比其他题解多了一句abs(a[i+1]-a[i])==1
因为我们不会在两个已经完美排序的两个盘子间断开,可以把他们看作一个整体了要转动就一起转,所以多加了一个判断。
2.
temp = eva;
reverse(a+ 1, a + i + 1);
if (abs(a[i] - a[i + 1]) == 1) temp = eva - 1;
这里避免了每次都调用一次估值函数来估值,直接找到两层递归间估值函数的联系做转移(能省点时间是一点)。
3.
if (temp + step <= maxstep)
{
dfs(i, temp,step + 1);if (d) return;
}
找到以后立马就返回别再搜了(这个其他题解里面好像也有)
直接给大家上代码吧
#include<bits/stdc++.h>
#define ll long long
using namespace std;
char ch1;
template<class T>
inline void rd(T& x) {
x = 0; bool w = 0;
ch1 = getchar();
while (!isdigit(ch1)) { ch1 == '-' && (w = 1), ch1 = getchar(); }
while (isdigit(ch1)) { x = (x << 1) + (x << 3) + (ch1 & 15), ch1 = getchar(); }
w && (x = (~x) + 1);
}
template<class T>
inline void wr(T x)
{
if (x < 0) x = -x, putchar('-');
if (x < 10) {
putchar(x + 48);
return;
}
T L = x / 10;
wr(L);
putchar(x - ((L << 1) + (L << 3)) + 48);
}
int n,a[20],b[20],maxstep;
bool d;
int evaluate()//估值函数
{
int cnt = 0;
for (int i = 1; i <= n; i++)
{
if (abs(a[i] - a[i + 1]) != 1)cnt++;
}
return cnt;
}
void dfs(int x,int eva,int step)
{
if (step == maxstep)//达到最大步数就返回
{
if (!eva)d = 1;
return;
}
int temp;
for (int i = 2; i<=n; i++)
{
if (i == x||abs(a[i+1]-a[i])==1)continue;//这里比其他题解多了一句abs(a[i+1]-a[i])==1,因为我们不会在两个已经完美排序的两个盘子间断开,可以把他们看作一个整体了
temp = eva;
reverse(a+ 1, a + i + 1);
if (abs(a[i] - a[i + 1]) == 1) temp = eva - 1;
if (temp + step <= maxstep)
{
dfs(i, temp,step + 1);if (d) return;
}
reverse(a+ 1, a + i + 1);
}
}
int main()
{
rd(n);
for (int i = 1; i <= n; i++)
{
rd(a[i]);
b[i] = a[i];
}
a[n + 1]= n + 1;//方便估值函数操作,如果不判断最后一个盘的话会TLE一个点
sort(b + 1, b + n + 1);
for (int i = 1; i <= n; ++i)
a[i] = lower_bound(b + 1, b + n + 1, a[i]) - b;//离散化
for (maxstep = 0;; maxstep++)
{
dfs(1, evaluate(), 0);
if (d) { cout << maxstep; return 0; }
}
}
/*
自己造的数据:
11
6 5 3 7 9 8 1 4 2 10 11
答案:8
13
7 4 5 3 9 6 2 8 1 10 13 11 12
答案:13
*/
推荐题目:骑士精神 八数码难题
太久没写洛谷了,橙名都蓝了┬_┬