搜索专题总结
2017-08-22 22:43
169 查看
经典例题随时可能更新…
其实这是不可能的,因为博主太懒了
人为加一个最大深度限定,再进行dfs,若在当前深度搜到了答案就输出,否则将最大深度增大。
优点:
①当题目所对应的搜索树是一棵无限树时(如经典例题1)。尽管在搜索过程中,有重复计算,但由于搜索树一般呈指数级增长,所以相对来说重复的计算量并不太大;
②当题目搜索的状态难以保存,或者空间复杂度较高时(如经典例题2)。它在搜索的实现上是dfs,结合其空间复杂度小和回溯的优点,就可以更好地处理问题。
在古埃及,人们使用单位分数的和(形如1/a的, a是自然数)表示一切有理数。 如:2/3=1/2+1/6,但不允许2/3=1/3+1/3,因为加数中有相同的。 对于一个分数a/b,表示方法有很多种,但是哪种最好呢? 首先,加数少的比加数多的好,其次,加数个数相同的,最小的分数越大越 好。 如: 19/45=1/3 + 1/12 + 1/180 19/45=1/3 + 1/15 + 1/45 19/45=1/3 + 1/18 + 1/30, 19/45=1/4 + 1/6 + 1/180 19/45=1/5 + 1/6 + 1/18. 最好的是最后一种,因为1/18比1/180,1/45,1/30,1/180都大。
[Input Description]
a b
[Output Description]
若干个数,自小到大排列,依次是单位分数的分母。
[Sample Input]
19 45
[Sample Output]
5 6 18
思路
用迭代加深算法,每次再次调用dfs时,必须传递剩余分数的分子和分母,也可以顺便约分一下。另外在处理的时候,要计算一下分母的上界和下界即可。
代码
即在原搜索算法的基础上加上一个估价函数,对扩展出的节点估价,以此进行剪枝。
优点:
对当前状态估价,预先估计当前节点可以扩展出ans的可能性,再取最有可能的进行扩展。是一种比较高级的剪枝方法,能减去很多不必要的计算量。值得注意的是,有时候估价函数需要写得很精细,比如一些求最短路的题目,才能保证ans一定是最优;而有时候估价函数要写得粗糙一些(如经典例题3),才能更有效地减小估价的时间复杂度。
对bfs算法的一种改进。将起点和终点同时加入两个广搜队列中,每次对两个队列搜索一层,并进行标记。当在某种状态发生重合时,即找到了解。
优点
可以大大地减少搜索的状态数以及时间。因为搜索的状态数一般是随着层数指数级增长的,单向bfs需要搜ans层,双向bfs则只需搜两个大概ans/2层。
给你一张地图,描述了不能走的地方(然而ghost却可以走),ghost,M和G的位置。每个ghost每秒分裂占领所有离它两格距离的格子,M每秒移动3格,G每秒移动1格。询问M和G能否在ghost碰到他们之前(刚好相遇时碰到也不行)相遇,能则输出最短用时,不能输出-1。
思路
由于ghost是不受地形限制的,那么可以直接以曼哈顿距离来判断t时某格是否被占领,因此,我们就只需要考虑M和G的移动方案了,然后它就成了一道很裸的双向bfs题。
值得注意的是,为了使得我们每次操作只搜完一层,可以在开始搜之前将队列的元素个数保存一下,具体见代码部分。
代码
其实这是不可能的,因为博主太懒了
迭代加深搜索
特点
描述:人为加一个最大深度限定,再进行dfs,若在当前深度搜到了答案就输出,否则将最大深度增大。
优点:
①当题目所对应的搜索树是一棵无限树时(如经典例题1)。尽管在搜索过程中,有重复计算,但由于搜索树一般呈指数级增长,所以相对来说重复的计算量并不太大;
②当题目搜索的状态难以保存,或者空间复杂度较高时(如经典例题2)。它在搜索的实现上是dfs,结合其空间复杂度小和回溯的优点,就可以更好地处理问题。
经典例题
1.埃及分数
[Description]在古埃及,人们使用单位分数的和(形如1/a的, a是自然数)表示一切有理数。 如:2/3=1/2+1/6,但不允许2/3=1/3+1/3,因为加数中有相同的。 对于一个分数a/b,表示方法有很多种,但是哪种最好呢? 首先,加数少的比加数多的好,其次,加数个数相同的,最小的分数越大越 好。 如: 19/45=1/3 + 1/12 + 1/180 19/45=1/3 + 1/15 + 1/45 19/45=1/3 + 1/18 + 1/30, 19/45=1/4 + 1/6 + 1/180 19/45=1/5 + 1/6 + 1/18. 最好的是最后一种,因为1/18比1/180,1/45,1/30,1/180都大。
[Input Description]
a b
[Output Description]
若干个数,自小到大排列,依次是单位分数的分母。
[Sample Input]
19 45
[Sample Output]
5 6 18
思路
用迭代加深算法,每次再次调用dfs时,必须传递剩余分数的分子和分母,也可以顺便约分一下。另外在处理的时候,要计算一下分母的上界和下界即可。
代码
#include <iostream> #include <cstring> #include <cstdio> using namespace std; typedef long long lld; const int len=101; lld ans[len],t[len],depth; bool flag=false; lld max(lld a,lld b){return a>b?a:b;} lld min(lld a,lld b){return a<b?a:b;} lld gcd(lld a,lld b){return b?gcd(b,a%b):a;} void dfs(lld a,lld b,lld k) { if(k==depth+1||a<0) return ; if(b%a==0&&b/a>t[k-1]) { t[k]=b/a; if(!flag||t[k]<ans[k])//若没找到解,或找到的解的最小分数更大,即其分母更小 { memcpy(ans,t,sizeof(t)); flag=true; } return ; }//下一个分数<当前分数,下一个分数分母大于上一个 lld s=max(b/a,t[k-1]+1); lld e=(depth-k+1)*b/a; if(flag&&e>=ans[depth]) e=ans[depth]-1; for(lld j=s;j<=e;j++)//枚举下一个分数的分母 { s=s; t[k]=j; lld m=gcd(b,j); dfs((a*j-b)/m,b*j/m,k+1); t[k]-=j; } } int main() { lld n,m; scanf("%d %d",&n,&m); t[0]=1; depth=0; while(!flag) { depth++; dfs(n,m,1); } for(int i=1;i<=depth;i++) printf("%d ",ans[i]); return 0; }
2.Tempter of the Bone II (HDU2128)
详细戳此Astar及IDA*
特点
描述[/b]:即在原搜索算法的基础上加上一个估价函数,对扩展出的节点估价,以此进行剪枝。
优点:
对当前状态估价,预先估计当前节点可以扩展出ans的可能性,再取最有可能的进行扩展。是一种比较高级的剪枝方法,能减去很多不必要的计算量。值得注意的是,有时候估价函数需要写得很精细,比如一些求最短路的题目,才能保证ans一定是最优;而有时候估价函数要写得粗糙一些(如经典例题3),才能更有效地减小估价的时间复杂度。
经典例题
3.Biggest Number (UVa11882)(A*)
详细戳此4.Editing a Book (UVa 11212)(IDA*)
详细戳此双向广搜
特点
描述对bfs算法的一种改进。将起点和终点同时加入两个广搜队列中,每次对两个队列搜索一层,并进行标记。当在某种状态发生重合时,即找到了解。
优点
可以大大地减少搜索的状态数以及时间。因为搜索的状态数一般是随着层数指数级增长的,单向bfs需要搜ans层,双向bfs则只需搜两个大概ans/2层。
经典例题
5.Nightmare II (HDU3085)
题目大意给你一张地图,描述了不能走的地方(然而ghost却可以走),ghost,M和G的位置。每个ghost每秒分裂占领所有离它两格距离的格子,M每秒移动3格,G每秒移动1格。询问M和G能否在ghost碰到他们之前(刚好相遇时碰到也不行)相遇,能则输出最短用时,不能输出-1。
思路
由于ghost是不受地形限制的,那么可以直接以曼哈顿距离来判断t时某格是否被占领,因此,我们就只需要考虑M和G的移动方案了,然后它就成了一道很裸的双向bfs题。
值得注意的是,为了使得我们每次操作只搜完一层,可以在开始搜之前将队列的元素个数保存一下,具体见代码部分。
代码
#include <iostream> #include <cstring> #include <cstdio> #include <queue> using namespace std; template <typename Tp> void read(Tp &x) { x=0; char ch=getchar(); while(ch<'0'||ch>'9') ch=getchar(); while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar(); } const int size=810; int v[4][2]={{-1,0},{0,1},{1,0},{0,-1}}; int z,n,m,mx,my,gx,gy,zx1,zy1,zx2,zy2,s; char map[size][size]; bool flag,vis[size][size][2]; queue<int> q[2]; int abs(int x){return x>0?x:-x;} bool check(int x,int y,int s)//判合法性有点复杂,于是就写在外面了 { if(x<1||x>n||y<1||y>m||map[x][y]=='X') return false; if(abs(x-zx1)+abs(y-zy1)<=2*s) return false; if(abs(x-zx2)+abs(y-zy2)<=2*s) return false; return true; } bool bfs(int k,int s) { int x,y,xx,yy,num=q[k].size(); while(num--)//每次只搜一层 { x=q[k].front()>>10; y=(x<<10)^q[k].front(); q[k].pop(); if(!check(x,y,s))//ghost先走 continue; for(int i=0;i<4;i++) { xx=x+v[i][0],yy=y+v[i][1]; if((!check(xx,yy,s))||vis[xx][yy][k]) continue; if(vis[xx][yy][k^1])//判是否被另一个队列标记过 return true; vis[xx][yy][k]=true; q[k].push(xx<<10|yy); } } return false; } int main() { read(z); while(z--) { read(n),read(m); flag=false; zx1=-1; for(int i=1;i<=n;i++,getchar()) for(int j=1;j<=m;j++) { map[i][j]=getchar(); if(map[i][j]=='M') mx=i,my=j; else if(map[i][j]=='G') gx=i,gy=j; else if(map[i][j]=='Z') { if(zx1==-1) zx1=i,zy1=j; else zx2=i,zy2=j; } } memset(vis,0,sizeof(vis)); while(!q[0].empty()) q[0].pop(); while(!q[1].empty()) q[1].pop(); q[0].push(mx<<10|my); q[1].push(gx<<10|gy); vis[mx][my][0]=true; vis[gx][gy][1]=true; s=1; while(q[0].size()||q[1].size()) { if(bfs(0,s)) {flag=true;break;} if(bfs(0,s)) {flag=true;break;} if(bfs(0,s)) {flag=true;break;} if(bfs(1,s)) {flag=true;break;} s++; } if(flag) printf("%d\n",s); else puts("-1"); } return 0; }