zoukankan      html  css  js  c++  java
  • 搜索篇

      搜索篇主要介绍深搜、广搜、剪枝和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
    ");
    
        }
    }
  • 相关阅读:
    学习笔记4
    学习笔记2
    学习笔记1
    树莓派与Arduino串口通信
    团队大作业第三周周报
    团队大作业第二周周报
    团队大作业第一周周报
    RTCSD_第三次作业
    RTCSD_第二次作业
    RTCSD_第一次作业
  • 原文地址:https://www.cnblogs.com/rhythmic/p/5687899.html
Copyright © 2011-2022 走看看