Uva1343-The Rotation Game-IDA*算法

原题https://uva.onlinejudge.org/external/13/1343.pdf


题意:  

有个#字型的棋盘,2行2列,一共24个格。

如图:每个格子是1或2或3,一共8个1,8个2,8个3.

有A~H一共8种合法操作,比如A代表把A这一列向上移动一个,最上面的格会补到最下面。

求:使中心8个格子数字一致的最少步骤,要输出具体的操作步骤及最终中心区域的数字。如果有多个解,输出字典序最小的操作步骤。


分析:

很明显的一个状态空间搜索问题,不过可以注意到,虽然每一个状态有八个可能的后续状态,随着操作数n的增加,总状态数 8^n 还是大得可怕。比如当n=11时,总状态为8^11 = 85亿。就算通过自己创建特制的哈希表进行状态判重,优化效果并不明显,因为最近一直在做状态空间搜索问题,即使用bfs+剪枝+哈希表,这些程序都无一例外的超时了,所以现在看到状态空间搜索问题,如果没有特别好的剪枝,我绝对不敢用bfs了.....

回到这道题,所有可以用bfs,回溯解决的问题,尤其是解答树的结点数没有明显上限的题,选择用迭代加深搜索算法都特别好用(原因可以参考我上一篇文章)。这里IDA*(迭代加深A*算法)其实说白了就是迭代加深+剪枝.

A*算法是对于每一步考虑 g(n) + h()和MAXD的关系。

稍微解释一下,g(n)是从起点到当前状态的总步数,MAXD是我们提前通过计算证明得到的最短路线总步数的上限,h()是启发函数,是整个算法的关键,我们设计的h()可以预估从当前状态到目标状态至少需要的步数。

这样,上面的关系式就很好理解了。g(n) + h() > MAXD 意味着当前已经走的步数+至少还需要的步数 > 我可以走的步数上限,这种状态,必然已经没有继续的必要,回溯。

对于这道题,可以注意到,对于每一次操作,我们最多可以让中心格子多一个目标数字,如果当前中心格子待整理的数字个数大于我们还可以走的步数,回溯。

这样,就得到了

    if (d + num_unordered() > MAXD) return false;

    这一核心剪枝公式。 剩下的就简单了。

代码只有52行,还是很简洁的。而且运行速度很快。过30组数据只用了126ms.

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <algorithm>
 4 using namespace std;
 5 const int MAXN = 24, LEN = 8;
 6 int board[LEN][LEN - 1] = { {0, 2, 6, 11, 15, 20, 22}, {1, 3, 8, 12, 17, 21, 23},
 7                         {10, 9, 8, 7, 6, 5, 4}, {19, 18, 17, 16, 15, 14, 13},
 8                         {23, 21, 17, 12, 8, 3, 1}, {22, 20, 15, 11, 6, 2, 0},
 9                         {13, 14, 15, 16, 17, 18, 19}, {4, 5, 6, 7, 8, 9, 10} };
10 int check_order[] = {6, 7, 8, 11, 12, 15, 16, 17}, a[MAXN], maxd;
11 char order[30];
12
13 int unordered() {
14     int n1 = 0, n2 = 0, n3 = 0;
15     for (int i = 0; i < LEN; i++)
16         if (a[check_order[i]] == 1)  n1++;
17         else if (a[check_order[i]] == 2) n2++;
18         else n3++;
19     return LEN - max(max(n1, n2), n3);
20 }
21
22 void rotate(int di) {
23     int t = a[board[di][0]];
24     for (int i = 1; i < LEN - 1; i++) a[board[di][i - 1]] = a[board[di][i]];
25     a[board[di][LEN - 2]] = t;
26 }
27
28 bool dfs(int d) {
29     int cnt = unordered();
30     if (!cnt) return true;
31     if (cnt + d > maxd) return false;
32     int temp[MAXN]; memcpy(temp, a, sizeof(a));
33     for (int i = 0; i < LEN; i++) {
34         rotate(i);
35         order[d] = i + ‘A‘;
36         if (dfs(d + 1)) return true;
37         memcpy(a, temp, sizeof(a));
38     }
39     return false;
40 }
41
42 int main() {
43     freopen("in", "r", stdin);
44     while (scanf("%d", &a[0]) && a[0]) {
45         for (int i = 1; i < MAXN; i++) scanf("%d", &a[i]);
46         if (!unordered()) { printf("No moves needed\n%d\n", a[6]); continue;}
47         for (maxd = 1;; maxd++) if (dfs(0)) break;
48         for (int i = 0; i < maxd; i++) printf("%c", order[i]);
49         printf("\n%d\n", a[6]);
50     }
51     return 0;
52 }

顺便纪念一下排第六(前面3个是virtual oj......)

时间: 2024-10-03 20:56:04

Uva1343-The Rotation Game-IDA*算法的相关文章

1343 - The Rotation Game (IDA*算法)

紫书上给的是状态空间搜索,其实本题也可以用IDA*算法,因为其符合IDA*的特点 : 求最小迭代次数 . 根据旋转的规律,我们可以用几个数组来储存向各个方向旋转时改变哪些量,用来维护旋转这个操作 .另外就是估价函数:当前出现在中间八个格子中次数最多的数字设为t ,那么剩下的迭代次数就是8 - t  , 如果它加上已经迭代的次数d > maxd ,则应当剪枝 . 另外想到了一个估算回溯法的时间复杂度的好办法 : 一般在每一层进入下一层的可能数设为t , 最大迭代次数设为 d  , 那么时间复杂度为

hdu-4127 Flood-it!(IDA*算法)

今天做的福州赛区区域赛的题目重现,一整场都在抠这道题仍然无法AC,时间卡的很紧,不过其实也是自己的搜索学的实在太差,紫书上刷的最少的就是第七章的题 . 我一开始就看出了这道题需要IDA*算法,但是昨天才看的还没能深入理解,通过赛后补这道题,感觉整体思路有了一个新的突破 . IDA*算法就是迭代加深搜索和A*算法的结合,迭代加深搜索非常简单,就是从小到大枚举深度上限,适合求解深度未知的或者像该题一样需要求最小迭代次数的题目 . A*算法的精髓是写一个估价函数, 如何写呢? 其实就是一个剪枝,通过计

12558 - Egyptian Fractions (HARD version)(IDA*算法)

IDA*算法,迭代加深搜索和A*算法的结合 . 迭代加深搜索适用于那些没有明显深度上限的题目,将深度从小到大枚举,直到找到最优解 ,减小了深搜的盲目性 . A*算法需要一个乐观估价函数,在这个函数里寻找一个代价最小的点去搜索,所以时间复杂度都浪费在这个上面了 . 代码如下: #include<bits/stdc++.h> using namespace std; typedef long long ll; int T,kase=0; ll v[10000+10],ans[10000+10],a

UVA-1343 The Rotation Game (IDA*)

题目大意:数字1,2,3都有八个,求出最少的旋转次数使得图形中间八个数相同.旋转规则:对于每一长行或每一长列,每次旋转就是将数据向头的位置移动一位,头上的数放置到尾部.若次数相同,则找出字典序最小旋转次序. 题目分析:IDA*,若当前在第cur层,中间八个数中1,2,3的个数分别为a,b,c.则d=8-max(a,b,c)便是达到目标还需要的理想次数,若cur+d>maxd,则剪枝.<入门经典>上提供了一种BFS的思路,分别以1,2,3为目标广搜3次,不过自己的码力还是太弱,并没有用那种

[poj2286]The Rotation Game (IDA*)

//第一次在新博客里发文章好紧张怎么办 //MD巨神早已在一个小时前做完了 The Rotation Game Time Limit: 15000MS Memory Limit: 150000K Total Submissions: 5950 Accepted: 1992 Description The rotation game uses a # shaped board, which can hold 24 pieces of square blocks (see Fig.1). The b

UVa1343 - The Rotation Game

因为8个转轮对应的位置比较没有规律,需要提前将这些位置存在数组中,方便旋转操作和回溯法的归位操作. 利用数组来人为储存没有规律的数字. IDA*合了bfs步数最少和dfs字典序最小的优点. #include<cstdio> #include<algorithm> #define maxn 500 using namespace std; int block[24]; int roller[8][7]={ {0,2,6,11,15,20,22},{1,3,8,12,17,21,23}

hdu 1667 The Rotation Game ( IDA* )

题目大意: 给你一个"井"子状的board,对称的由24个方块组成,每个方块上有123三个数字中的一个.给你初始状态,共有八种变换方式,求字典序最小的最短的的变换路径使得,board中间的八个方块上数字相同.(建议看下题目中的图就懂啦). IDA*搜索. 我是干脆用结构体保存搜索状态(当然这样很占空间了,可能也耗时间,不过这题15s/150M的时空限制我也是醉了).保存一个board temp,一个搜索路径path,搜索深度n,以及一个内置的估值函数h().h()返回的是8减这八个方块

UVa1603 The Rotation Game (IDA*)

链接:http://acm.hust.edu.cn/vjudge/problem/36627分析:感觉这题前面的处理比较麻烦.首先把图中的位置从左到右从上到下编号,然后用一个二维数组记录8个方向上各个位置的编号,枚举最大深度maxd,乐观估计函数为至少需要移动次数,因为每次移动最多改变一个位置上的数字,所以中间8个位置除了出现次数最多的数字外的数字个数为x的话那么就至少要移动x次,枚举move8个方向再递归如果check为true则成功找到解返回,否则将a复位继续枚举,枚举完8个方向找不到解则失

UVa 1343 The Rotation Game(IDA*)

主要是设计乐观估计函数来减枝 假设中心区域有6个2,2个3,那肯定是消掉3最好,毕竟就两个. 那么理想情况下,旋转一次就能把一个3变成2,那么最少操作2次. 我们用h()来计算最少还要操作几次,其原理是假设中心区域都放1或2或3,返回至少操作的次数中最小的数 maxd是假设最多能操作的数; d是已经操作的数; 那么就可以得出乐观估计函数   h()+d>maxd 其含义为 : 若  至少还要操作次数  加上  已经操作的次数  大于  最多总共操作的次数就退出 . 其次就是节点的处理了,编个号数

11212 - Editing a Book(IDA*算法)

又一道迭代加深搜索,从小到大枚举上限 .   关键的剪枝部分是写出启发函数,这个比较难.. 不过每次剪切后,不正确的数字个数最多减三还是很好理解的,因为我们算不正确数字个数的方法是看当前数字+1是不是等于下一个数字 . 所以每次剪切最多只有3个数字的后继数字发生了改变.  那么 剪枝条件就显而易见了 . 代码如下: #include<bits/stdc++.h> using namespace std; const int maxn = 15; int n,a[15],kase = 0,max