初涉启发式搜索
高效的一种搜索方式(或许可以叫做一种优化?
什么是启发式搜索
「启发式搜索」这个名字,听上去有些空洞。
想象一下你在去NOIP考场的路上堵车了,但是比赛马上要开始了!你心急火燎地想要找到一条最短路径,但由于此时并不知道整张图(所有到达考场的可行路径),你被迫只能使用搜索的方式。此刻你化出了无数个分身,每个人都往一个特定的方向前进,试图寻找到达考场的最短时间。如果按照传统的BFS/DFS的话,实际上过程中搜索了很多丝毫没有作用的状态——有可能大部分的分身都一直往郊区走去,越走越远,而你却不得不浪费资源去维护他们的状态!这就是「盲搜」。
一种能够使你更快到达考场的方式就是使用「启发式搜索」。
你让每一个分身都维护3个数据:已经花的时间g;预计还要多少时间h;预计最短总共要花时间f。
考虑这个$h$的计算之前先来想一想:$h$在不一定完全等于真正要花时间的情况下(因为h是估计出来的,并不能保证花了h时间之后一定到达),它是应该小于真正的用时$h'$还是要大于真正的用时$h'$。
- $h<h'$:估计偏大,“悲观地认为”能够按时到达的点将会超时:搜索范围偏小,搜得快但是不一定是最优解。
- $h>h'$:估计偏小,“保守地认为”大部分点能够按时到达(但如果即使这样保守都还会超时,就肯定要舍去了):搜索范围偏大,效率较低但一定是最优解。
那么由此可得我们应该在小于等于实际距离的情况下,尽可能接近实际距离。
回到寻找NOIP考场路径的问题,我们根据题目条件,自然想到用欧几里得距离充当h()函数。这如同现在有四个方向:一个朝着考场走去;一个背离考场走去;还有两个沿着无关的方向走去。大部分情况下,第一种状态总是能够较为快速地走到终点的,因此我们优先搜索第一种状态。当然,也不排除其他的特殊情况——
所以我们要在优先搜索f()小的情况下,把所有判断可行的状态都搜索一遍。(当然,这也是搜索必须满足的性质)
启发式搜索板子题
【A*】poj1915Knight Moves
Description
Mr Somurolov, fabulous chess-gamer indeed, asserts that no one else but him can move knights from one position to another so fast. Can you beat him?
The Problem
Your task is to write a program to calculate the minimum number of moves needed for a knight to reach one point from another, so that you have the chance to be faster than Somurolov.
For people not familiar with chess, the possible knight moves are shown in Figure 1.
Input
Next follow n scenarios. Each scenario consists of three lines containing integer numbers. The first line specifies the length l of a side of the chess board (4 <= l <= 300). The entire board has size l * l. The second and third line contain pair of integers {0, ..., l-1}*{0, ..., l-1} specifying the starting and ending position of the knight on the board. The integers are separated by a single blank. You can assume that the positions are valid positions on the chess board of that scenario.
Output
题意
求骑士从(x1,y1)->(x2,y2)的最短步数
题目分析
裸的A*题,h()使用欧几里得距离。
1 #include<cmath> 2 #include<queue> 3 #include<cstdio> 4 #include<cstring> 5 #include<algorithm> 6 const int dx[] = {0, 1, 1, 2, 2, -1, -1, -2, -2}; 7 const int dy[] = {0, 2, -2, 1, -1, 2, -2, 1, -1}; 8 const int maxn = 303; 9 10 struct node 11 { 12 int x,y,step; 13 int f,g,h; 14 bool operator < (node t) const 15 { 16 if (f==t.f) 17 return step > t.step; 18 return f > t.f; 19 } 20 }k; 21 int sx,sy,tx,ty,tt,n,ans; 22 int vis[maxn][maxn]; 23 std::priority_queue<node> q; 24 25 void clear(std::priority_queue<node> &q) 26 { 27 std::priority_queue<node> emt; 28 swap(emt, q); 29 } 30 bool range(node x) 31 { 32 return x.x>=0 && x.y>=0 && x.x<n && x.y<n; 33 } 34 int dis(node x) 35 { 36 return (int)(sqrt((x.x-tx)*(x.x-tx)*1.0+(x.y-ty)*(x.y-ty)*1.0)*1000); 37 } 38 void Astar() 39 { 40 while (q.size()) 41 { 42 node tt = q.top(); 43 q.pop(); 44 if (vis[tt.x][tt.y] && vis[tt.x][tt.y] <= tt.step) continue; 45 vis[tt.x][tt.y] = tt.step; 46 if (tt.x == tx && tt.y == ty) 47 { 48 ans = tt.step; 49 return; 50 } 51 for (int i=1; i<=8; i++) 52 { 53 node t; 54 t.x = tt.x+dx[i], t.y = tt.y+dy[i]; 55 if (range(t) && !vis[t.x][t.y]) 56 { 57 t.h = dis(t),t.g = tt.g + 2236; 58 t.step = tt.step+1; 59 t.f = t.g+t.h; 60 q.push(t); 61 } 62 } 63 } 64 } 65 int main() 66 { 67 scanf("%d",&tt); 68 for (int rr=1; rr<=tt; rr++) 69 { 70 memset(vis, 0, sizeof vis); 71 clear(q); 72 scanf("%d",&n); 73 scanf("%d%d%d%d",&sx,&sy,&tx,&ty); 74 k.x = sx, k.y = sy; 75 k.g = 0, k.step = 0; 76 k.h = dis(k), k.f = k.h; 77 q.push(k); 78 Astar(); 79 printf("%d\n",ans); 80 } 81 return 0; 82 }
【A*】luoguP1379 八数码难题
题目描述
在3×3的棋盘上,摆有八个棋子,每个棋子上标有1至8的某一数字。棋盘中留有一个空格,空格用0来表示。空格周围的棋子可以移到空格中。要求解的问题是:给出一种初始布局(初始状态)和目标布局(为了使题目简单,设目标状态为123804765),找到一种最少步骤的移动方法,实现从初始布局到目标布局的转变。
输入输出格式
输入格式:
输入初始状态,一行九个数字,空格用0表示
输出格式:
只有一行,该行只有一个数字,表示从初始状态到目标状态需要的最少移动次数(测试数据中无特殊无法到达目标状态数据)
题目分析
此题保证有解,那么就很容易了。
如果存在无解情况,则需要事先判断。参见http://www.cppblog.com/menjitianya/archive/2014/06/23/207386.html SGU 139。
1 #include<set> 2 #include<queue> 3 #include<cstdio> 4 #include<cctype> 5 #include<cstring> 6 #include<algorithm> 7 const int dx[] = {0, 1, -1, 0, 0}; 8 const int dy[] = {0, 0, 0, 1, -1}; 9 const int ff[] = {0,1,2,3,1,2,3,1,2,3}; 10 const int gg[] = {0,1,1,1,2,2,2,3,3,3}; 11 12 struct node 13 { 14 int a[13],b[13],zr; 15 int f,g,h,step; 16 bool operator < (const node &x) const 17 { 18 if (f == x.f) 19 return x.step < step; 20 return f > x.f; 21 } 22 inline bool fallsDown() 23 { 24 if (zr != 5) return 0; 25 int f[] = {5, 1, 2, 3, 6, 9, 8, 7, 4}; 26 for (int i=0; i<9; i++) 27 if (a[i]!=f[i]) 28 return 0; 29 return 1; 30 } 31 inline int reVal() 32 { 33 int num = 0; 34 for (int i=0; i<9; i++) 35 num = num*10+a[i]; 36 return num; 37 } 38 void print() 39 { 40 int f[13]; 41 for (int i=0; i<9; i++) 42 f[a[i]] = i; 43 for (int i=1; i<=9; i++) 44 { 45 printf("%d ",f[i]); 46 if (i%3==0) puts(""); 47 } 48 puts(""); 49 } 50 }k; 51 std::set<int> vis; 52 std::priority_queue<node> q; 53 int ans; 54 55 inline int abs(int a){return a>0?a:-a;} 56 inline int evaluate(const node &t) 57 { 58 register int cnt = 0,i,x,y,tx,ty; 59 for (i=1; i<=9; i++) 60 { 61 x = gg[i], y = ff[i]; 62 tx = gg[t.a[i]], ty = ff[t.a[i]]; 63 cnt += abs(x-tx)+abs(y-ty); 64 } 65 return cnt; 66 } 67 void swaps(int &a, int &b) 68 { 69 register int t = a; 70 a = b, b = t; 71 } 72 inline int read() 73 { 74 char ch = getchar(); 75 for (; !isdigit(ch); ch = getchar()); 76 return ch-48; 77 } 78 inline bool range(int a, int b) 79 { 80 return a>0 && a<4 && b>0 && b<4; 81 } 82 void Astar() 83 { 84 register int zrx,zry,i,tx,ty,nzr; 85 while (q.size()) 86 { 87 node tt = q.top(); 88 q.pop(); 89 vis.insert(tt.reVal()); 90 if (tt.fallsDown()) 91 { 92 ans = tt.step; 93 return; 94 } 95 zrx = (tt.zr-1)/3+1, zry = (tt.zr-1)%3+1; 96 for (i=1; i<=4; i++) 97 { 98 tx = zrx+dx[i], ty = zry+dy[i]; 99 if (range(tx, ty)) 100 { 101 node ts = tt; 102 nzr = tx*3+ty-3; 103 swaps(ts.a[ts.b[ts.zr]], ts.a[ts.b[nzr]]); 104 swaps(ts.b[ts.zr], ts.b[nzr]); 105 if (!vis.count(ts.reVal())){ 106 ts.zr = nzr; 107 ts.step = tt.step+1; 108 ts.g = ts.step; 109 ts.h = evaluate(ts); 110 ts.f = ts.g+ts.h; 111 q.push(ts); 112 } 113 } 114 } 115 } 116 } 117 int main() 118 { 119 for (int i=1; i<=9; i++) 120 { 121 int num = read(); 122 k.a[num] = i; 123 k.b[i] = num; 124 if (!num) k.zr = i; 125 } 126 k.h = evaluate(k), k.f = k.h; 127 k.step = 0; 128 q.push(k); 129 Astar(); 130 printf("%d\n",ans); 131 return 0; 132 }
【IDA*/A*】bzoj1085: [SCOI2005]骑士精神
Description
在一个5×5的棋盘上有12个白色的骑士和12个黑色的骑士, 且有一个空位。在任何时候一个骑士都能按照骑
士的走法(它可以走到和它横坐标相差为1,纵坐标相差为2或者横坐标相差为2,纵坐标相差为1的格子)移动到空
位上。 给定一个初始的棋盘,怎样才能经过移动变成如下目标棋盘: 为了体现出骑士精神,他们必须以最少的步
数完成任务。
Input
第一行有一个正整数T(T<=10),表示一共有N组数据。接下来有T个5×5的矩阵,0表示白色骑士,1表示黑色骑
士,*表示空位。两组数据之间没有空行。
Output
对于每组数据都输出一行。如果能在15步以内(包括15步)到达目标状态,则输出步数,否则输出-1。
Sample Input
10110
01*11
10111
01001
00000
01011
110*1
01110
01010
00100
Sample Output
-1
题目分析
1 #include<set> 2 #include<queue> 3 #include<cmath> 4 #include<cctype> 5 #include<cstdio> 6 #include<cstring> 7 //const int dx[] = {0, 1, 2, 1, 2, -1, -2, -1, -2}; 8 //const int dy[] = {0, 2, 1, -2, -1, 2, 1, -2, -1}; 9 const int dx[] = {0, -2, -2, -1, 1, -1, 1, 2, 2}; 10 const int dy[] = {0, -1, 1, 2, 2, -2, -2, -1, 1}; 11 const int ff[] = {0,1,1,1,1,1,2,2,2,2,2,3,3,3,3,3,4,4,4,4,4,5,5,5,5,5}; 12 const int gg[] = {0,1,2,3,4,5,1,2,3,4,5,1,2,3,4,5,1,2,3,4,5,1,2,3,4,5}; 13 const int standard[6][6] = {{0,0,0,0,0,0},{0,2,2,2,2,2},{0,1,2,2,2,2},{0,1,1,0,2,2},{0,1,1,1,1,2},{0,1,1,1,1,1}}; 14 15 struct node 16 { 17 int step,a[13][13]; 18 int f,g,h,zr; 19 bool operator < (node x) const 20 { 21 if (x.f == f) 22 return x.step < step; 23 return x.f < f; 24 } 25 inline int reVal() 26 { 27 int sum = 0; 28 for (int i=1; i<=5; i++) 29 for (int j=1; j<=5; j++) 30 sum = sum*3+a[i][j]; 31 return sum; 32 } 33 void print() 34 { 35 for (int i=1; i<=5; i++) 36 { 37 for (int j=1; j<=5; j++) 38 printf("%d ",a[i][j]); 39 puts(""); 40 } 41 printf("step:%d f:%d g:%d h:%d\n",step,f,g,h); 42 puts(""); 43 } 44 }k; 45 std::priority_queue<node> q; 46 std::set<int> vis; 47 int tts,ans; 48 49 int read() 50 { 51 char ch = getchar(); 52 for (; (!isdigit(ch))&&(ch!='*'); ch = getchar()); 53 if (isdigit(ch)) return ch-'0'+1; 54 return 0; 55 } 56 inline int evaluate(node a) 57 { 58 int cnt = 0; 59 register int i,j; 60 for (i=1; i<=5; i++) 61 for (j=1; j<=5; j++) 62 if (standard[i][j]!=a.a[i][j]) 63 cnt++; 64 return cnt; 65 } 66 inline bool range(int x, int y) 67 { 68 return x>0 && y>0 && x<=5 && y<=5; 69 } 70 inline void swap(int &a, int &b) 71 { 72 int t = a; 73 a = b, b = t; 74 } 75 void clear(std::priority_queue<node> &q) 76 { 77 std::priority_queue<node> emt; 78 std::swap(emt, q); 79 } 80 void Astar() 81 { 82 clear(q); 83 q.push(k); 84 vis.clear(); 85 while (q.size()) 86 { 87 node tt = q.top(); 88 q.pop(); 89 if (tt.f > 16) continue; 90 vis.insert(tt.reVal()); 91 int x = ff[tt.zr], y = gg[tt.zr]; 92 if (evaluate(tt)==0) 93 { 94 ans = tt.step; 95 break; 96 } 97 for (int i=1; i<=8; i++) 98 { 99 int tx = x+dx[i], ty = y+dy[i]; 100 if (range(tx, ty)) 101 { 102 node ss = tt; 103 swap(ss.a[tx][ty], ss.a[x][y]); 104 ss.zr = tx*5+ty-5; 105 if (!vis.count(ss.reVal())){ 106 ss.step++; 107 ss.g = ss.step; 108 ss.h = evaluate(ss); 109 ss.f = ss.g+ss.h; 110 q.push(ss); 111 } 112 } 113 } 114 } 115 } 116 int main() 117 { 118 scanf("%d",&tts); 119 for (int rr=1; rr<=tts; rr++) 120 { 121 for (int i=1; i<=5; i++) 122 for (int j=1; j<=5; j++) 123 { 124 k.a[i][j] = read(); 125 if (!k.a[i][j]) k.zr = i*5+j-5; 126 } 127 k.g = 0,k.step = 0; 128 k.h = evaluate(k),k.f = k.h; 129 ans = -1; 130 Astar(); 131 printf("%d\n",ans); 132 } 133 return 0; 134 }
1 #include<set> 2 #include<queue> 3 #include<cmath> 4 #include<cctype> 5 #include<cstdio> 6 #include<cstring> 7 //const int dx[] = {0, 1, 2, 1, 2, -1, -2, -1, -2}; 8 //const int dy[] = {0, 2, 1, -2, -1, 2, 1, -2, -1}; 9 const int dx[] = {0, -2, -2, -1, 1, -1, 1, 2, 2}; 10 const int dy[] = {0, -1, 1, 2, 2, -2, -2, -1, 1}; 11 const int ff[] = {0,1,1,1,1,1,2,2,2,2,2,3,3,3,3,3,4,4,4,4,4,5,5,5,5,5}; 12 const int gg[] = {0,1,2,3,4,5,1,2,3,4,5,1,2,3,4,5,1,2,3,4,5,1,2,3,4,5}; 13 const int standard[6][6] = {{0,0,0,0,0,0},{0,2,2,2,2,2},{0,1,2,2,2,2},{0,1,1,0,2,2},{0,1,1,1,1,2},{0,1,1,1,1,1}}; 14 15 struct node 16 { 17 int step,a[13][13]; 18 int f,g,h,zr; 19 bool operator < (node x) const 20 { 21 if (x.f == f) 22 return x.step < step; 23 return x.f < f; 24 } 25 int reVal() 26 { 27 int sum = 0; 28 for (int i=1; i<=5; i++) 29 for (int j=1; j<=5; j++) 30 sum = sum*3+a[i][j]; 31 return sum; 32 } 33 void print() 34 { 35 for (int i=1; i<=5; i++) 36 { 37 for (int j=1; j<=5; j++) 38 printf("%d ",a[i][j]); 39 puts(""); 40 } 41 printf("step:%d f:%d g:%d h:%d\n",step,f,g,h); 42 puts(""); 43 } 44 }k; 45 std::priority_queue<node> q; 46 std::set<int> vis; 47 int tts,ans; 48 bool done; 49 50 int read() 51 { 52 char ch = getchar(); 53 for (; (!isdigit(ch))&&(ch!='*'); ch = getchar()); 54 if (isdigit(ch)) return ch-'0'+1; 55 return 0; 56 } 57 int evaluate(node a) 58 { 59 int cnt = 0; 60 for (int i=1; i<=5; i++) 61 for (int j=1; j<=5; j++) 62 if (standard[i][j]!=a.a[i][j]) 63 cnt++; 64 return cnt; 65 } 66 bool range(int x, int y) 67 { 68 return x>0 && y>0 && x<=5 && y<=5; 69 } 70 void swap(int &a, int &b) 71 { 72 int t = a; 73 a = b, b = t; 74 } 75 void clear(std::priority_queue<node> &q) 76 { 77 std::priority_queue<node> emt; 78 std::swap(emt, q); 79 } 80 void Astar(int depth) 81 { 82 if (done) return; 83 clear(q); 84 q.push(k); 85 vis.clear(); 86 while (q.size()) 87 { 88 node tt = q.top(); 89 q.pop(); 90 if (tt.f > depth+1) continue; //若大于深度就退出 91 vis.insert(tt.reVal()); 92 int x = ff[tt.zr], y = gg[tt.zr]; 93 if (evaluate(tt)==0) 94 { 95 done = 1; 96 ans = tt.step; 97 break; 98 } 99 for (int i=1; i<=8; i++) 100 { 101 int tx = x+dx[i], ty = y+dy[i]; 102 if (range(tx, ty)) 103 { 104 node ss = tt; 105 swap(ss.a[tx][ty], ss.a[x][y]); 106 ss.zr = tx*5+ty-5; 107 if (!vis.count(ss.reVal())){ 108 ss.step++; 109 ss.g = ss.step; 110 ss.h = evaluate(ss); 111 ss.f = ss.g+ss.h; 112 q.push(ss); 113 } 114 } 115 } 116 } 117 } 118 int main() 119 { 120 scanf("%d",&tts); 121 for (int rr=1; rr<=tts; rr++) 122 { 123 for (int i=1; i<=5; i++) 124 for (int j=1; j<=5; j++) 125 { 126 k.a[i][j] = read(); 127 if (!k.a[i][j]) k.zr = i*5+j-5; 128 } 129 k.g = 0,k.step = 0; 130 k.h = evaluate(k),k.f = k.h; 131 done = 0;ans = -1; 132 for (int i=0; i<=15; i++) //与A*不同之处 133 Astar(i); 134 printf("%d\n",ans); 135 } 136 return 0; 137 }
【IDA*】HDU1560DNA sequence
Problem Description
For example, given "ACGT","ATGC","CGTT" and "CAGT", you can make a sequence in the following way. It is the shortest but may be not the only one.
Input
Output
题意
构造一个最短的字符串,使得所有给定的n个字符串都是它的字串。
题目分析
自然想到IDA*应对这种问题。那么h()怎么写呢?
一种比较巧妙的方式是用max(lens[i]-match[i])作为h(),其中lens[i]表示第i个字符串的长度;match[i]表示第i个字符串已经匹配了多少位。
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 const char seq[] = " ACGT"; 5 6 int tt,n,lst; 7 int lens[13],match[13]; 8 char mp[13][13]; 9 bool done; 10 11 int evaluate() 12 { 13 int ret = 0; 14 for (int i=1; i<=n; i++) 15 ret = std::max(ret, lens[i]-match[i]); 16 return ret; 17 } 18 void Astar(int left) 19 { 20 if (evaluate()==0) done=1; 21 if (!left || done || left < evaluate()) return; 22 int bkp[13]; 23 for (int i=1; i<=n; i++) bkp[i] = match[i]; 24 bool ck; 25 for (int i=1; i<=4; i++) 26 { 27 ck = 0; 28 for (int j=1; j<=n; j++) 29 if (mp[j][match[j]]==seq[i]) ck = 1,match[j]++; 30 if (!ck) continue; 31 Astar(left-1); 32 if (done) return; 33 for (int j=1; j<=n; j++) match[j] = bkp[j]; 34 } 35 } 36 int main() 37 { 38 scanf("%d",&tt); 39 for (; tt; tt--) 40 { 41 memset(match, 0, sizeof match); 42 memset(lens, 0, sizeof lens); 43 lst = 0; 44 done = 0; 45 scanf("%d",&n); 46 for (int i=1; i<=n; i++) 47 { 48 scanf("%s",mp[i]); 49 lens[i] = strlen(mp[i]); 50 lst = std::max(lst, lens[i]); 51 } 52 for (;;) 53 { 54 Astar(lst); 55 if (done) break; 56 lst++; 57 } 58 printf("%d\n",lst); 59 } 60 return 0; 61 }
END