搜索专题

1.BFS

1.1 BFS模板

#include<cstdio>  
#include<cstring>  
#include<queue>  
#include<algorithm>  
using namespace std;  
const int maxn=100;  
bool  vst[maxn][maxn];  //访问标记  
int dir[4][2]={0,1,0,-1,1,0,-1,0}; //方向向量,有些题目不需要  
struct State //BFS 队列中的状态数据结构  
{   
 int x,y; //坐标位置  
 int Step_Counter; //搜索步数统计器  
};  
State a[maxn];  
/*   是否需要视题目而定
bool CheckState(State s) //约束条件检验  
{  
   if(!vst[s.x][s.y]&&...) //满足条件  
     return 1;  
   else    //约束条件冲突  
   return 0;  
}  

*/
void bfs(State st)  
{  
  queue<State>q;  //BFS队列  
  State now,next;  //定义2个状态,当前和下一个  
  st.Step_Counter=0; //计数器清零  
  q.push(st);   //入队 ,,,,,,另一种不带参数的写法是将初始的状态进入队列
   vst[st.x][st.y]=1;  //访问标记  
  while(!q.empty())  
{  
  now=q.front(); //取队首元素进行扩展  
  if(now==G) //出现目标态,此时为Step_Counter的最小值,可以退出即可  
  {  
        ......     //做相关处理  
      return;  
   }  
   for(int i=0;i<4;i++)  
   {  
       next.x=now.x+dir[i][0]; //按照规则生成下一个状态  
       next.y=now.y+dir[i][1];  
       next.Step_Counter=now.Step_Counter+1;  //计数器加1  
     if(CheckState(next))  //如果状态满足约束条件则入队  
     {  
       q.push(next);  
       vst[next.x][next.y]=1;   //访问标记  
      }  
}  
   q.pop();  //队首元素出队  
}  

1.2 例题

1.2.1 非常可乐
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 19932 Accepted Submission(s): 8063

Problem Description
大家一定觉的运动以后喝可乐是一件很惬意的事情,但是seeyou却不这么认为。因为每次当seeyou买了可乐以后,阿牛就要求和seeyou一起分享这一瓶可乐,而且一定要喝的和seeyou一样多。但seeyou的手中只有两个杯子,它们的容量分别是N 毫升和M 毫升 可乐的体积为S (S<101)毫升 (正好装满一瓶) ,它们三个之间可以相互倒可乐 (都是没有刻度的,且 S==N+M,101>S>0,N>0,M>0) 。聪明的ACMER你们说他们能平分吗?如果能请输出倒可乐的最少的次数,如果不能输出”NO”。

Input
三个整数 : S 可乐的体积 , N 和 M是两个杯子的容量,以”0 0 0”结束。

Output
如果能平分的话请输出最少要倒的次数,否则输出”NO”。

Sample Input

7 4 3
4 1 3
0 0 0

Sample Output

NO
3

Author

seeyou

解题代码

#include<iostream>
#include<queue>
#include<cstdio>
#include<cstring>
using namespace std;

const int maxn=500;
//方向向量,有些题不需要
int dir[4][2]={0,1,0,-1,1,0,-1,0};
//BFS队列中的状态数据结构
int vis[maxn][maxn];
struct State
{
    int S,N,M;
    int step;
};

int s,n,m;
int res=-1;//保存最终结果

//约束条件检验
//bool check(State s);
queue<State>q;
void bfs()
{

    //定义两个状态,现在和下一个
    //将初始状态进入队列
    memset(vis,0,sizeof(vis));
    State now,next;
    q.push((State){s,0,0,0});
    vis[0][0]=1;
    while(!q.empty())
    {
        //取队首元素进行扩展
        now=q.front();
        //判断是否出现目标状态,此时为step最小值,可以退出即可
        if((now.S==s/2&&now.M==s/2)||(now.S==s/2&&now.N==s/2)||(now.M==s/2&&now.N==s/2))
        {
            res=now.step;
            //将剩余队列清空
            while(!q.empty())
            {
                q.pop();
            }
            return;
        }
        q.pop();
        //开始进行搜索过程
        //本题主要是对瓶S,N,M有六种操作 S->N,S->M,N->S,N->M,M->S,M->N;
        //如果N瓶还未装满
        if(now.N<n)
        {
            //此过程又可以分为S可以装满N和S不可以装满N以及M可以装满N和M不可以装满N
            if(now.S>n-now.N&&!vis[n][now.M])
            {
                vis[n][now.M]=1;
                q.push((State){now.S-n+now.N,n,now.M,now.step+1});
            }
            if(now.S<n-now.N&&!vis[now.N+now.S][now.M])
            {
                vis[now.S+now.N][now.M]=1;
                q.push((State){0,now.S+now.N,now.M,now.step+1});
            }
            if(now.M>n-now.N&&!vis[n][now.M-n+now.N])
            {
                vis[n][now.M-n+now.N]=1;
                q.push((State){now.S,n,now.M-n+now.N,now.step+1});
            }
            if(now.M<n-now.N&&!vis[now.M+now.N][0])
            {
                vis[now.M+now.N][0]=1;
                q.push((State){now.S,now.M+now.N,0,now.step+1});
            }

        }
        //如果M瓶还没装满
        if(now.M<m)
        {
             if(now.S>m-now.M&&!vis[now.N][m])
            {
                vis[now.N][m]=1;
                q.push((State){now.S-m+now.M,now.N,m,now.step+1});
            }
           if(now.S<m-now.M&&!vis[now.N][now.S+now.M])
            {
                vis[now.N][now.S+now.M]=1;
                q.push((State){0,now.N,now.S+now.M,now.step+1});
            }
            if(now.N>m-now.M&&!vis[now.N-m+now.M][m])
            {
                vis[now.N-m+now.M][m]=1;
                q.push((State){now.S,now.N-m+now.M,m,now.step+1});
            }
            if(now.N<m-now.M&&!vis[0][now.M+now.N])
            {
                vis[0][now.M+now.N]=1;
                q.push((State){now.S,0,now.M+now.N,now.step+1});
            }
        }
        //对于S瓶而言,其他瓶不可能把S装满
        if(now.S<s)
        {
            if(now.N<s-now.S&&!vis[0][now.M])
            {
                vis[0][now.M]=1;
                q.push((State){now.N+now.S,0,now.M,now.step+1});
            }
            if(now.M<s-now.S&&!vis[now.N][0])
            {
                vis[now.N][0]=1;
                q.push((State){now.S+now.M,now.N,0,now.step+1});
            }
        }
    }
}
int main()
{
    while(~scanf("%d%d%d",&s,&n,&m))
    {
        res=-1;
        if(s==0&&n==0&&m==0)
            break;
        if(s%2!=0)
        {
             printf("NO
");
             continue;
        }
        bfs();
       if(res==-1)
       {
           printf("NO
");
           continue;
       }
       else
       {
           printf("%d
",res);
       }


    }
    return 0;
}

1.2.2胜利大逃亡(续)
Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 9963 Accepted Submission(s): 3593

Problem Description
Ignatius再次被魔王抓走了(搞不懂他咋这么讨魔王喜欢)……

这次魔王汲取了上次的教训,把Ignatius关在一个n*m的地牢里,并在地牢的某些地方安装了带锁的门,钥匙藏在地牢另外的某些地方。刚开始Ignatius被关在(sx,sy)的位置,离开地牢的门在(ex,ey)的位置。Ignatius每分钟只能从一个坐标走到相邻四个坐标中的其中一个。魔王每t分钟回地牢视察一次,若发现Ignatius不在原位置便把他拎回去。经过若干次的尝试,Ignatius已画出整个地牢的地图。现在请你帮他计算能否再次成功逃亡。只要在魔王下次视察之前走到出口就算离开地牢,如果魔王回来的时候刚好走到出口或还未到出口都算逃亡失败。

Input

每组测试数据的第一行有三个整数n,m,t(2<=n,m<=20,t>0)。接下来的n行m列为地牢的地图,其中包括:

. 代表路
* 代表墙 @ 代表Ignatius的起始位置 ^ 代表地牢的出口 A-J 代表带锁的门,对应的钥匙分别为a-j a-j 代表钥匙,对应的门分别为A-J

每组测试数据之间有一个空行。

Output

针对每组测试数据,如果可以成功逃亡,请输出需要多少分钟才能离开,如果不能则输出-1。

Sample Input

4 5 17
@A.B.
a*.*.
*..*^
c..b*

4 5 16
@A.B.
a*.*.
*..*^
c..b*

Sample Output

16
-1

思路:
BFS+状态压缩
对钥匙状态压缩,因为有10个钥匙,所以钥匙串为10位的2进制数串。
在搜索过程中会碰到3种有效情况,碰到路径,碰到钥匙,碰到门;

  • 碰到路径,进行正常的标记操作
  • 碰到钥匙,对钥匙串进行或运算,把碰到的钥匙加到钥匙串
  • 碰到门,进行与运算,查看是否有相应的钥匙
#include<iostream>
#include<string.h>
#include<stdio.h>
#include<queue>
using namespace std;
const int maxn=25;
char ma[maxn][maxn];

bool vis[maxn][maxn][1025];//10种钥匙的状态
int m,n,t;
int sx,sy;
struct State
{
    int x,y;
    int step,key;
};


int dir[4][2]={1,0,-1,0,0,1,0,-1};


bool check(int x,int y)
{
    if(x>=0&&x<n&&y>=0&&y<m&&ma[x][y]!='*')
        return true;
    else return false;
}


int bfs()
{
    int k;
    memset(vis,false,sizeof(vis) );
    queue<State>q;
    State now,next;
    now.x=sx;
    now.y=sy;
    now.key=0;
    now.step=0;


    q.push(now);
    vis[sx][sy][0]=1;
    while(!q.empty())
    {
        now=q.front();
        //出现目标状态
        q.pop();
        if(ma[now.x][now.y]=='^')
        {
            if(now.step<t)
                return now.step;
        }
        if(now.step>=t) break;
        for(int i=0;i<4;i++)
        {
            next.x=now.x+dir[i][0];
            next.y=now.y+dir[i][1];
            next.step=now.step+1;
            next.key=now.key;
            if(check(next.x,next.y))
            {
                //碰到三种情况 碰到钥匙 碰到门 碰到路径
                //碰到门的情况,钥匙与门进行与操作
                if(ma[next.x][next.y]<='J'&&ma[next.x][next.y]>='A')
                {
                    k=ma[next.x][next.y]-'A';
                    if((next.key>>k)&1&&!vis[next.x][next.y][next.key])
                    {
                        vis[next.x][next.y][next.key]=true;
                        q.push(next);
                    }
                }
                //碰到钥匙的情况,采用或运算放入钥匙
                else if(ma[next.x][next.y]<='j'&&ma[next.x][next.y]>='a')
                {
                    k=1<<(ma[next.x][next.y]-'a');
                    next.key|=k;
                    if(!vis[next.x][next.y][next.key])
                    {
                        vis[next.x][next.y][next.key]=true;
                        q.push(next);
                    }
                }
                else
                {
                    if(!vis[next.x][next.y][next.key])
                    {
                        vis[next.x][next.y][next.key]=true;
                        q.push(next);
                    }
                }
            }
        }
    }
    return -1;

}


int main()
{
    int i,j;
    freopen("test.txt","r",stdin);
    while(scanf("%d%d%d",&n,&m,&t)!=EOF)
    {
        for(int i=0;i<n;i++)
        {
            scanf("%s",ma[i]);
            for(j=0;j<m;j++)
            {
                if(ma[i][j]=='@')
                {
                    sx=i;
                    sy=j;
                }
            }
        }
        printf("%d
",bfs());
    }

    return 0;
}

2. DFS

2.1 DFS模板

#include<cstdio>
#include<cstring>
#include<cstdlib>
using namespace std;
const int maxn=100;
bool vst[maxn][maxn];   //访问标记  
// bool vis[maxn];     //一维状态标记
int map[maxn][maxn];  //坐标范围
int dir[4][2]={0,1,0,-1,1,0,-1,0};  //方向向量,(x,y)周围的四个方向
bool CheckEdge(intx,int y)    //边界条件和约束条件的判断
{
   if(!vst[x][y]&&...)  //满足条件
      return 1;
   else     // 与约束条件冲突
     return  0;
}

void dfs(int x,int y)
{
    vst[x][y]=1; //标记该节点被访问过

      if(map[x][y]==G)  //出现目标态G
      {
           ......  //做相应处理

       return;
      }
     for(int i=0;i<4;i++)
    {
      if(CheckEdge(x+dir[i][0],y+dir[i][1])) //按照规则生成下一个节点
       dfs(x+dir[i][0],y+dir[i][1]);
     }
     return;//没有下层搜索节点,回溯
}
int main()
{
   ......
  return 0;
}

2.2 DFS 例题

2.2.1 棋盘问题
Time Limit: 1000MS Memory Limit: 10000K
Total Submissions: 58295 Accepted: 28027
Description

在一个给定形状的棋盘(形状可能是不规则的)上面摆放棋子,棋子没有区别。要求摆放时任意的两个棋子不能放在棋盘中的同一行或者同一列,请编程求解对于给定形状和大小的棋盘,摆放k个棋子的所有可行的摆放方案C。
Input

输入含有多组测试数据。
每组数据的第一行是两个正整数,n k,用一个空格隔开,表示了将在一个n*n的矩阵内描述棋盘,以及摆放棋子的数目。 n <= 8 , k <= n
当为-1 -1时表示输入结束。
随后的n行描述了棋盘的形状:每行有n个字符,其中 # 表示棋盘区域, . 表示空白区域(数据保证不出现多余的空白行或者空白列)。
Output

对于每一组数据,给出一行输出,输出摆放的方案数目C (数据保证C<2^31)。
Sample Input

2 1
#.
.#
4 4
...#
..#.
.#..
#...
-1 -1

Sample Output

2
1

解题代码:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
using namespace std;


const int maxn=500;
//设置访问标记
//bool vis[maxn][maxn];
bool vis[maxn];//每一列棋子的访问标志
char m[maxn][maxn];
//设置方向向量
int dir[4][2]={1,0,-1,0,0,1,0,-1};
//已放棋子的数目
int cnt;
//放置棋子的方案数
int sum;
int k,n;
//边界约束条件
/*
bool CheckEdge(int x,int y)
{
    if(!vis[x][y]&&.....) //满足条件
        return 1;
    else                   //与约束条件冲突
        return 0;
}

*/
void dfs(int s)
{

    //检测是否满足条件
    if(cnt==k)
    {
        sum++;
        return;
    }
    else
    {
        //判断是否越界
        if(s>=n)return;
        else
        {
            //开始搜索过程
            //首先尝试将棋子放在0-n-1的某一列
            for(int i=0;i<n;i++)
            {
                //如果当前列有棋盘而且没有被标记
                if(m[s][i]=='#'&&!vis[i])
                {
                    vis[i]=1;
                    cnt++;
                    //继续回溯搜索下一步
                    dfs(s+1);
                    //经过一轮搜索没结果则回溯
                    cnt--;
                    vis[i]=0;

                }

            }
            dfs(s+1);
        }
    }
}
int main()
{
    while(cin>>n>>k)
    {
        memset(vis,0,sizeof(vis));
        sum=cnt=0;
        if(n==-1&&k==-1)
            break;
        for(int i=0;i<n;i++)
            for(int j=0;j<n;j++)
                cin>>m[i][j];
        dfs(0);
        cout<<sum<<endl;
    }
}
原文地址:https://www.cnblogs.com/bryce1010/p/9386806.html