您的位置:首页 > 其它

搜索专题总结

2017-08-22 22:43 169 查看
经典例题随时可能更新…

其实这是不可能的,因为博主太懒了

迭代加深搜索

特点

描述:

人为加一个最大深度限定,再进行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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: