搜索专题总结

经典例题随时可能更新…
其实这是不可能的,因为博主太懒了


迭代加深搜索

特点

描述:
人为加一个最大深度限定,再进行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*

特点

描述:
即在原搜索算法的基础上加上一个估价函数,对扩展出的节点估价,以此进行剪枝。
优点:
对当前状态估价,预先估计当前节点可以扩展出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;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值