搜索篇

  搜索篇主要介绍深搜、广搜、剪枝和A*算法,下面通过具体的题目进行一一呈现。

  Q1(Problem source : 百练2815):

  描述

请你编写一个程序,计算城堡一共有多少房间,最大的房间有多大。城堡被分割成mn(m≤50,n≤50)个方块,每个方块可以有0~4面墙。输入程序从标准输入设备读入数据。第一行是两个整数,分别是南北向、东西向的方块数。
在接下来的输入行里,每个方块用一个数字(0≤p≤50)描述。用一个数字表示方块周围的墙,1表示西墙,2表示北墙,4表示东墙,8表示南墙。每个方块用代表其周围墙的数字之和表示。
城堡的内墙被计算两次,方块(1,1)的南墙同时也是方块(2,1)的北墙。输入的数据保证城堡至少有两个房间。输出城堡的房间数、城堡中最大房间所包括的方块数。结果显示在标准输出设备上。
样例输入
4 
7 
11 6 11 6 3 10 6 
7 9 6 13 5 15 5 
1 10 12 7 13 7 5 
13 11 10 8 10 12 13 

样例输出

5
9

 分析:很典型的dfs问题,数据规模不大也不会用到剪枝,这里需要注意的一个运算符优先级的小细节是用位运算符&和==运算符的时候,前面的位运算符要加小括号。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 51;
int Map[maxn][maxn];
int visit[maxn][maxn];
int m , n;
int dfs(int i , int j)
{
     int num = 1;
     visit[i][j] = 1;
     if((Map[i][j] & 1) == 0 && j - 1 >= 1 && visit[i][j-1] == 0)
     {
          num += dfs(i,j-1);
     }
     if((Map[i][j] & 2) == 0 && i - 1 >= 1 && visit[i-1][j] == 0)
     {
          num += dfs(i-1,j);
     }
     if((Map[i][j] & 4) == 0 && j + 1 <= n && visit[i][j+1] == 0)
     {
          num += dfs(i,j+1);
     }
     if((Map[i][j] & 8) == 0 && i + 1 <= m && visit[i+1][j] == 0)
     {
          num += dfs(i+1,j);
     }

     return num;
}
int main()
{

      while(scanf("%d",&m) != EOF)
      {
           scanf("%d",&n);
            for(int i = 1;i <= m;i++)
                  for(int j = 1;j <= n;j++)
                      scanf("%d",&Map[i][j]);

            memset(visit , 0 , sizeof(visit));

            int num = 0;
            int Max = 0;
           // printf("%d ",dfs(1,1));


            for(int i = 1;i <= m;i++)
                  for(int j = 1;j <= n;j++)
                  {
                        if(visit[i][j] == 0)
                        {

                            num++;
                            Max = max(Max , dfs(i,j));
                        }
                         else  continue;
                 }

            printf("%d
%d
",num,Max);


      }
}

 Q2(Problem source : poj 3984 || 百练ACM暑假课练习题10):

Description

定义一个二维数组:
int maze[5][5] = {
 	0, 1, 0, 0, 0,
 	0, 1, 0, 1, 0,
 	0, 0, 0, 0, 0,
 	0, 1, 1, 1, 0,
 	0, 0, 0, 1, 0,
 };
它表示一个迷宫,其中的1表示墙壁,0表示可以走的路,只能横着走或竖着走,不能斜着走,要求编程序找出从左上角到右下角的最短路线。

Input

一个5 × 5的二维数组,表示一个迷宫。数据保证有唯一解。

Output

左上角到右下角的最短路径,格式如样例所示。

Sample Input

0 1 0 0 0
0 1 0 1 0
0 0 0 0 0
0 1 1 1 0
0 0 0 1 0

Sample Output

(0, 0)
(1, 0)
(2, 0)
(2, 1)
(2, 2)
(2, 3)
(2, 4)
(3, 4)
(4, 4)
 分析:能够看到很明显的搜索思路,但是要打印路径。这里如果用bfs,可以直接得到最短路(在队尾一旦发现到达终点,即可终止),另一方面bfs在一个队列中很容易记录路径。打印路径采取递归的方法。
另外这道题目有个小坑在于,队列的长度一定要开够。下面的代码是手动实现队列的,当然STL更方便。
#include<cstdio>
#include<cstring>
using namespace std;
struct node
{
    int x , y , pre;
}q[200];
int Map[5][5];
int dx[4] = {1 , 0 , -1 , 0};
int dy[4] = {0 , 1 ,  0 ,-1};
void print(int i)
{
    if(q[i].pre == -1)  {printf("(0, 0)
");return;}
    else
    {
        print(q[i].pre);
        printf("(%d, %d)
",q[i].x,q[i].y);
        return;
    }
}
void bfs(int i , int j)
{
    int head = 0 , tail = 1;
    int a , b;
    q[head].x = i;
    q[head].y = j;
    q[head].pre = -1;
    int flag = 0;
      while(1)
      {

           for(int i = 0;i < 4;i++)
           {
                a = q[head].x + dx[i];
                b = q[head].y + dy[i];
               if(a < 0 || a > 4 || b < 0 || b > 4 || Map[a][b] == 1)
                            continue;
               q[tail].x = a;
               q[tail].y = b;
               q[tail].pre = head;
               tail++;

               if( a == 4 && b == 4)
                   {flag = 1;print(tail-1);break;}
            }
      if(flag == 1)  break;

              head++;

    }

}
int main()
{

   for(int i = 0;i < 5;i++)
      for(int j = 0;j < 5;j++)
           scanf("%d",&Map[i][j]);

   bfs(0,0);
   return 0;

}

  Q3: (Problem source : poj 1321):

  描述:

  在一个给定形状的棋盘(形状可能是不规则的)上面摆放棋子,棋子没有区别。要求摆放时任意的两个棋子不能放在棋盘中的同一行或者同一列,请编程求解对于给定形状和大小的棋盘,摆放k个棋子的所有可行的摆放方案C。输入输入含有多组测试数据。 每组数据的第一行是两个正整数,n k,用一个空格隔开,表示了将在一个n*n的矩阵内描述棋盘,以及摆放棋子的数目。 n <= 8 , k <= n 当为-1 -1时表示输入结束。 随后的n行描述了棋盘的形状:每行有n个字符,其中 # 表示棋盘区域, . 表示空白区域(数据保证不出现多余的空白行或者空白列)。

输出:对于每一组数据,给出一行

输出,输出摆放的方案数目C (数据保证C<2^31)。

  分析:基于矩阵图利用dfs求解填充方案数的问题,这里其技巧点就是如何根据题设限制进行“有序”的深度优先搜索,一个要点是不能够重复计数,令一个要点是不能够漏计数。   考虑到它是行列不重复,我们进行“有序”的深搜的策略是在第i行中,选出一个可选位置,标记visit[j]表示访问了该列,随后在第i+1行中重复如上步骤,期间设置参量用于计数。这是我们“有序”的深搜策略。   另一个层面是不能漏,在第i行中,我们可能有x种选择情况,我们要一次遍历然后进入到搜索树的下一层,但是这里一定不要忘记设置一个“可选项”是“该行不选位置”,下面代码注释部分就是笔者一开始的错误写法。笔者一开始想要依次从第i行开始构造可行的方案,但是这样没有给深搜过程中留出“第i行不选该位置”的选项,这就对导致漏掉一些满足的情况。

  参考代码如下:

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
const int maxn = 10;
char Map[maxn][maxn];
int num , cnt;
int n , k;
int visit[maxn];
void dfs(int i)
{
     if(cnt == k)  {num++;return;}
     if(i >= n)   return;
       for(int j = 0 ;j < n;j++)
       {
                 if(Map[i][j] == '#' && visit[j] == 0)
                 {
                     visit[j] = 1;
                     cnt++;
                     dfs(i+1);
                     visit[j] = 0;
                     cnt--;
                 }
       }

       dfs(i+1);//第i行填棋子


}
int main()
{
      while(scanf("%d %d",&n,&k))
      {
          if(n == -1 && k == -1)  break;
           for(int i = 0;i < n;i++)
                cin>>Map[i];


                num = 0;
                cnt = 0;

                dfs(0);
                printf("%d
",num);
                //for(int i = 0;i < n;i++)
                         //{
                             //...初始化
                             //dfs(i)
                         //}

      }
}

Q4(Problem source : 百练暑假ACM练习题07):

  路飞他们伟大航路行程的起点是罗格镇,终点是拉夫德鲁(那里藏匿着“唯一的大秘宝”——ONE PIECE)。而航程中间,则是各式各样的岛屿。

  因为伟大航路上的气候十分异常,所以来往任意两个岛屿之间的时间差别很大,从A岛到B岛可能需要1天,而从B岛到A岛则可能需要1年。当然,任意两个岛之间的航行时间虽然差别很大,但都是已知的。

  现在假设路飞一行从罗格镇(起点)出发,遍历伟大航路中间所有的岛屿(但是已经经过的岛屿不能再次经过),最后到达拉夫德鲁(终点)。假设他们在岛上不作任何的停留,请问,他们最少需要花费多少时间才能到达终点?

  输入:

  输入数据包含多行。 第一行包含一个整数N(2 < N ≤ 16),代表伟大航路上一共有N个岛屿(包含起点的罗格镇和终点的拉夫德鲁)。其中,起点的编号为1,终点的编号为N。 之后的N行每一行包含N个整数,其中,第i(1 ≤ i ≤ N)行的第j(1 ≤ j ≤ N)个整数代表从第i个岛屿出发到第j个岛屿需要的时间t(0 < t < 10000)。第i行第i个整数为0。

  输出:输出为一个整数,代表路飞一行从起点遍历所有中间岛屿(不重复)之后到达终点所需要的最少的时间。

  分析:  其实这道题目非常具有迷惑性,因为它涉及一个最短路,但是需要遍历所有的点,而且也不是典型的回到起点的TSP,因此这里基于dfs进行搜索维护权值最小,只不过需要基于dp子问题将搜索过程中的各个状态记录下来用于最优化剪枝。
  这里记录每个状态的一个基本技巧就是利用位运算,设置dp[i][S]表示出发后,当前在i城市,已经走过了集合S(元素是城市)的最短路,这里利用一个整型的二进制形式来表示集合j,最大不会超过1<<17.
  最优化剪枝的两个策略:
  策略一:这个策略很好理解,当我们已经得到一种方案的权值和是Min的时候,在以后的搜索过程中发现路径权值和已经超过了Min,显然不需要继续搜下去。
  策略二:其实是基于标准TSP的递归方程(虽然题设有些不同但是这个递归方程是通用的),dp[i][S] = min{dp[i][S-{j}] + dis[i][j]},因此我们在深搜的过程中,时刻记录dp[i][S],在搜索过程一旦dp[i][S-{j}] + dis[i][j]大于之前保留的dp[i][S],即可进行剪枝。
  参考代码如下。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 1<<17;
int dp[17][maxn];
int Min;
int dis[20][20];
int visit[20];
int n;

void dfs(int now ,int pass)
{
    if(dp[now][pass] > Min)  return;
    if((pass | (1<<n-1)) == ((1<<n) - 1))
        {
           Min = min(Min,dp[now][pass] + dis[now][n]);
           return;
        }

    for(int i = 2;i < n;i++)
    {
           if(visit[i] == 0)
            {
                 int new_pass = pass | (1 << (i-1));

                  if(now == 1)
                    {
                      visit[i] = 1;
                      dp[i][new_pass] = dis[1][i];
                      dfs(i , new_pass);
                      visit[i] = 0;
                    }
                  else
                    {
                      if( dp[i][new_pass] == 0 || dp[i][new_pass] > (dp[now][pass] + dis[now][i]))
                      {
                            visit[i] = 1;
                            dp[i][new_pass] = dp[now][pass] + dis[now][i];
                            dfs(i , new_pass);
                            visit[i] = 0;
                      }
                    }
             }
     }
}

int main()
{
    while(scanf("%d",&n) != EOF)
       {
           Min = 9999999;
           memset(dp , 0 , sizeof(dp));
           memset(visit , 0 , sizeof(visit));

             for(int i = 1;i <= n;i++)
                   for(int j = 1;j <= n;j++)
                       scanf("%d",&dis[i][j]);

                dfs(1,1);
                printf("%d
",Min);
       }
       return 0;
}

Q4(Problem source : 百练 ACM暑假作业题11):

已知一张地图(以二维矩阵的形式表示)以及佐助和鸣人的位置。地图上的每个位置都可以走到,只不过有些位置上有大蛇丸的手下,需要先打败大蛇丸的手下才能到这些位置。鸣人有一定数量的查克拉,每一个单位的查克拉可以打败一个大蛇丸的手下。假设鸣人可以往上下左右四个方向移动,每移动一个距离需要花费1个单位时间,打败大蛇丸的手下不需要时间。如果鸣人查克拉消耗完了,则只可以走到没有大蛇丸手下的位置,不可以再移动到有大蛇丸手下的位置。佐助在此期间不移动,大蛇丸的手下也不移动。请问,鸣人要追上佐助最少需要花费多少时间?

输入

输入的第一行包含三个整数:M,N,T。代表M行N列的地图和鸣人初始的查克拉数量T。0 < M,N < 200,0 ≤ T < 10 后面是M行N列的地图,其中@代表鸣人,+代表佐助。*代表通路,#代表大蛇丸的手下。

输出

输出包含一个整数R,代表鸣人追上佐助最少需要花费的时间。如果鸣人无法追上佐助,则输出-1。


  分析:很典型的dfs遍历维护最短路径的问题,这里涉及最优剪枝和可行性剪枝,一般有路径限制的dfs都可以用到可行性剪枝,这里的路径限制参量是查克拉。
  初步的代码如下.

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

const int maxn = 205;
char Map[maxn][maxn];
int visit[maxn][maxn];
int m , n , c;
int dx[4] = {1 , -1 , 0 , 0};
int dy[4] = {0 , 0 ,  1 , -1};
int xs , ys , xe , ye;
int Min;
void dfs(int x , int y ,int  c,int step)
{

    if(c < 0)      return;//可行性剪枝,一旦查克拉少于0 , 停止搜索
    if(step > Min) return;//最优性剪枝,一旦当前步数比当前维护的最小值还要大,停止搜索。


    for(int i = 0;i < 4;i++)//四个方向
    {
          int xx = x + dx[i];
          int yy = y + dy[i];
          if(xx < 0 || xx >= m || yy < 0 || yy >= n || visit[xx][yy] == 1)  continue;//越界
          if(Map[xx][yy] == '#')//遇到大蛇丸
          {
                visit[xx][yy] = 1;
                dfs(xx , yy ,c - 1 , step + 1);
                visit[xx][yy] = 0;
          }
          if(Map[xx][yy] == '*')//通路
          {
                visit[xx][yy] = 1;
                dfs(xx , yy , c , step + 1);
                visit[xx][yy] = 0;
          }
          if(Map[xx][yy] == '+')//遇到佐助,深搜到最底层,返回
          {
              Min = min(Min , step + 1);
              return;
          }

    }
}
int main()
{
      while(scanf("%d%d%d",&m,&n,&c) != EOF)
      {
            Min = 9999999999;
            memset(visit , 0 , sizeof(visit));
            for(int i = 0;i < m ; i++)
                   cin>>Map[i];

             for(int i = 0;i < m;i++)
                      for(int j = 1;j < n;j++)
                      {
                          if(Map[i][j] == '@')
                               xs = i , ys = j;
                          if(Map[i][j] == '+')
                               xe = i , ye = j;
                      }
                 visit[xs][ys] = 1;
                dfs(xs , ys , c , 0);
                printf("%d
",Min);
      }
}

   ps分析:其实这是典型的bfs搜矩阵路径的最短路,这里体现的就是bfs空间换dfs的时间,但是这里需要注意的是bfs去重过程不仅仅是该点坐标两个维度,还有一个维度的限制——查克拉。

  参考代码如下:

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

const int maxn = 205;
struct node
{
    int x , y , ckl , step;
     node (int xx,int yy, int cc, int ss):x(xx), y(yy), ckl(cc), step(ss){};

};
char Map[maxn][maxn];
int n , m , c;
int dx[4] = {1 , -1 , 0 , 0};
int dy[4] = {0 , 0  , 1 , -1};
int xs , ys , Min;
int visit[maxn][maxn][12];


bool bfs(int xs , int ys)
{
    queue<node> q;
    struct node n0(xs , ys , c , 0);

    q.push(n0);

    while(!q.empty())
    {
         struct  node temp = q.front();

         q.pop();
        for(int i = 0;i < 4;i++)
        {
                int xx = temp.x + dx[i];
                int yy = temp.y + dy[i];
                //printf("%d %d
",xx,yy);
           if(xx<0 || xx >= m || yy <0 ||yy>=n)  continue;

           if(Map[xx][yy] == '+')
           {
               Min = temp.step + 1;
               return true;
           }
           if(Map[xx][yy] == '*' && visit[xx][yy][temp.ckl] == 0)
           {

                visit[xx][yy][temp.ckl] = 1;
                q.push(node(xx , yy , temp.ckl , temp.step + 1));
           }



            if(Map[xx][yy] == '#' && visit[xx][yy][temp.ckl-1]== 0 && temp.ckl >= 1)
           {
               visit[xx][yy][temp.ckl-1] = 1;
               q.push(node(xx , yy , temp.ckl - 1 , temp.step + 1));
           }


        }

    }
    return false;
}
int main()
{
    while(scanf("%d%d%d",&m,&n,&c) != EOF)
    {
         for(int i = 0;i < m;i++)
         for(int j = 0;j < n;j++)
            {
                  cin>>Map[i][j];
         if(Map[i][j] == '@')
               xs = i , ys = j;
            }
                        //printf("%d %d",xs , ys);
            memset(visit , 0 , sizeof(visit));

            visit[xs][ys][c] = 1;
            if(bfs(xs , ys) != false)  printf("%d
",Min);
            else                       printf("-1
");

    }
}
原文地址:https://www.cnblogs.com/rhythmic/p/5687899.html