zoukankan      html  css  js  c++  java
  • 机试指南第六章-搜索-例题自解

    枚举:

    枚举是最简单也是最直白的搜索方式,它依次尝试搜索空间中所有的解,测试其是否符合条件,若符合则输出答案,否则继续测试下一组解。

    例6.1 百鸡问题

    #include<iostream>
    using namespace std;
    
    int main()
    {
        int n;
        while (cin >> n) 
        {
            for (int x = 0; x <= 100; x++)
            {
                for (int y = 0; y <= 100 - x; y++)
                {
                    int z = 100 - x - y;
                    if (x * 5 * 3 + y * 3 * 3 + z <= n * 3)
                    {
                        cout << "x=" << x << ",y=" << y << ",z=" << z << endl;
                    }
                }
            }
        }
        return 0;
    }

    广度优先搜索(BFS:breadth-first search):

     例6.2 胜利大逃亡(广搜+剪枝)

    AC代码:

    #include<cstdio>
    #include<queue>
    using namespace std;
    
    bool mark[50][50][50];//标记数组,用来确保每个结点只被访问一次
    int maze[50][50][50];//保存立方体的信息
    struct N //状态结构体
    {
        int x, y, z; //结点坐标
        int t;//达到该结点的最短时间
    };
    
    queue<N> Q;//队列符合操作顺序,先读入起点,也先从起点开始遍历
    int go[][3] = { 1,0,0,-1,0,0,0,1,0,0,-1,0,0,0,1,0,0,-1 };//六个方向
    
    int BFS(int a, int b, int c)//广搜,返回最少耗时
    {
        while (Q.empty() == false)//当队列中仍有元素可以拓展时,继续循环
        {
            N now = Q.front();//得到队头的状态
            Q.pop();//从队列中弹出队头的状态
            for (int i = 0; i < 6; i++)//依次拓展相应的六个结点
            {
                int nx = now.x + go[i][0];
                int ny = now.y + go[i][1];
                int nz = now.z + go[i][2];//计算新坐标
                if (nx < 0 || nx >= a || ny < 0 || ny >= b || nz < 0 || nz >= c)continue;//新坐标在立方体之外则丢弃该坐标
                if (maze[nx][ny][nz] == 1)continue;//该坐标为墙,丢弃
                if (mark[nx][ny][nz] == true)continue;//该坐标已被访问,丢弃
                N tmp;//新的状态
                tmp.x = nx;
                tmp.y = ny;
                tmp.z = nz;//新状态坐标
                tmp.t = now.t + 1;//新状态的耗时
                Q.push(tmp);//将新状态加入队列
                mark[nx][ny][nz] = true;
                if (nx == a - 1 && ny == b - 1 && nz == c - 1)return tmp.t;//该坐标为终点,直接返回其耗时
            }
        }
        return -1;//所有状态查找完之后仍然找不到所需坐标,返回-1
    }
    
    int main()
    {
        int T;
        scanf("%d", &T);
        while (T--)
        {
            int a, b, c, t;
            scanf("%d%d%d%d", &a, &b, &c, &t);
            for (int i = 0; i < a; i++)
            {
                for (int j = 0; j < b; j++)
                {
                    for (int k = 0; k < c; k++)
                    {
                        scanf("%d", &maze[i][j][k]);
                        mark[i][j][k] = false;
                    }
                }
            }
            while (Q.empty() == false) Q.pop();//清空上一个队列
            mark[0][0][0] = true;//标记起点
            N tmp;
            tmp.t = tmp.x = tmp.y = tmp.z = 0;
            Q.push(tmp);//将初始状态放入队列
            int ans = BFS(a, b, c);//广搜开始启动
            if (ans <= t)printf("%d
    ", ans); //小于等于时间要求,输出
            else  printf("-1
    ");
        }
        return 0;
    }
    View Code

    TLE代码(使用cin超时了,数据量过大,scanf更快)

    #include<iostream>
    #include<queue>
    using namespace std;
    
    bool mark[50][50][50];//标记数组,用来确保每个结点只被访问一次
    int maze[50][50][50];//保存立方体的信息
    struct N //状态结构体
    {
        int x, y, z; //结点坐标
        int t;//达到该结点的最短时间
    };
    
    queue<N> Q;//队列符合操作顺序,先读入起点,也先从起点开始遍历
    int go[][3] = { 1,0,0,-1,0,0,0,1,0,0,-1,0,0,0,1,0,0,-1 };//六个方向
    
    int BFS(int a, int b, int c)//广搜,返回最少耗时
    {
        while (Q.empty() == false)//当队列中仍有元素可以拓展时,继续循环
        {
            N now = Q.front();//得到队头的状态
            Q.pop();//从队列中弹出队头的状态
            for (int i = 0; i < 6; i++)//依次拓展相应的六个结点
            {
                int nx = now.x + go[i][0];
                int ny = now.y + go[i][1];
                int nz = now.z + go[i][2];//计算新坐标
                if (nx < 0 || nx >= a || ny < 0 || ny >= b || nz < 0 || nz >= c)continue;//新坐标在立方体之外则丢弃该坐标
                if (maze[nx][ny][nz] == 1)continue;//该坐标为墙,丢弃
                if (mark[nx][ny][nz] == true)continue;//该坐标已被访问,丢弃
                N tmp;//新的状态
                tmp.x = nx;
                tmp.y = ny;
                tmp.z = nz;//新状态坐标
                tmp.t = now.t + 1;//新状态的耗时
                Q.push(tmp);//将新状态加入队列
                mark[nx][ny][nz] = true;
                if (nx == a - 1 && ny == b - 1 && nz == c - 1)return tmp.t;//该坐标为终点,直接返回其耗时
            }
        }
        return -1;//所有状态查找完之后仍然找不到所需坐标,返回-1
    }
    
    int main()
    {
        int T;
        cin >> T;
        while (T--)
        {
            int a, b, c, t;
            cin >> a >> b >> c >> t;
            for (int i = 0; i < a; i++)
            {
                for (int j = 0; j < b; j++)
                {
                    for (int k = 0; k < c; k++)
                    {
                        cin >> maze[i][j][k];
                        mark[i][j][k] = false;
                    }
                }
            }
            while (Q.empty() == false) Q.pop();//清空上一个队列
            mark[0][0][0] = true;//标记起点
            N tmp;
            tmp.t = tmp.x = tmp.y = tmp.z = 0;
            Q.push(tmp);//将初始状态放入队列
            int ans = BFS(a, b, c);//广搜开始启动
            if (ans <= t)cout << ans << endl;//小于等于时间要求,输出
            else cout << -1 << endl;
        }
        return 0;
    }
    View Code

     例6.3 非常可乐

    题目描述:

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

    输入:

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

    输出:

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

    样例输入:

    7 4 3 
    4 1 3 
    0 0 0

    样例输出:

    NO
    3

    解题代码:

    #include<cstdio>
    #include<queue>
    using namespace std;
    
    struct N
    {
        int a, b, c;//每个杯子中可乐的体积
        int t;//得到该体积组倾倒的次数
    };
    
    queue<N> Q;
    bool mark[101][101][101];//对体积组(x,y,z)进行标记,确保每个状态只访问一次
    
    void TO(int &a, int sa, int &b, int sb)//倾倒函数,由容积为sa的杯子倒往容积为sb的杯子,a和b为杯中原有可乐的体积,当函数调用完毕后,为各自杯中可乐的新体积 
    {
        if (sb - b >= a)//a可以全部倒给b
        {
            b += a;
            a = 0;
        }
        else
        {
            a -= sb - b;
            b = sb;
        }
    }
    
    int BFS(int s, int n, int m)
    {
        while (Q.empty() == false)//队列非空,重复循环
        {
            N now = Q.front();//取队头状态
            Q.pop();//弹出队头状态
            int a, b, c;//a, b, c临时保存三个杯子中可乐的体积
            a = now.a;
            b = now.b;
            c = now.c;
            TO(a, s, b, n);
            if (mark[a][b][c] == false)//若该体积组尚未出现
            {
                mark[a][b][c] = true;//标记该体积组
                N tmp;
                tmp.a = a;
                tmp.b = b;
                tmp.c = c;
                tmp.t = now.t + 1;//生成新的状态
                if (a == s / 2 && b == s / 2)return tmp.t;
                if (c == s / 2 && b == s / 2)return tmp.t;
                if (a == s / 2 && c == s / 2)return tmp.t;//若该状态已经为平分状态,返回该状态耗时
                Q.push(tmp);//否则放入队列
            }
            a = now.a;
            b = now.b;
            c = now.c;
            TO(b, n, a, s);//b倒向a
            if (mark[a][b][c] == false)//若该体积组尚未出现
            {
                mark[a][b][c] = true;//标记该体积组
                N tmp;
                tmp.a = a;
                tmp.b = b;
                tmp.c = c;
                tmp.t = now.t + 1;//生成新的状态
                if (a == s / 2 && b == s / 2)return tmp.t;
                if (c == s / 2 && b == s / 2)return tmp.t;
                if (a == s / 2 && c == s / 2)return tmp.t;//若该状态已经为平分状态,返回该状态耗时
                Q.push(tmp);//否则放入队列
            }
            a = now.a;
            b = now.b;
            c = now.c;
            TO(a, s, c, m);//a倒向c
            if (mark[a][b][c] == false)//若该体积组尚未出现
            {
                mark[a][b][c] = true;//标记该体积组
                N tmp;
                tmp.a = a;
                tmp.b = b;
                tmp.c = c;
                tmp.t = now.t + 1;//生成新的状态
                if (a == s / 2 && b == s / 2)return tmp.t;
                if (c == s / 2 && b == s / 2)return tmp.t;
                if (a == s / 2 && c == s / 2)return tmp.t;//若该状态已经为平分状态,返回该状态耗时
                Q.push(tmp);//否则放入队列
            }
            a = now.a;
            b = now.b;
            c = now.c;
            TO(c, m, a, s);//c倒向a
            if (mark[a][b][c] == false)//若该体积组尚未出现
            {
                mark[a][b][c] = true;//标记该体积组
                N tmp;
                tmp.a = a;
                tmp.b = b;
                tmp.c = c;
                tmp.t = now.t + 1;//生成新的状态
                if (a == s / 2 && b == s / 2)return tmp.t;
                if (c == s / 2 && b == s / 2)return tmp.t;
                if (a == s / 2 && c == s / 2)return tmp.t;//若该状态已经为平分状态,返回该状态耗时
                Q.push(tmp);//否则放入队列
            }
            a = now.a;
            b = now.b;
            c = now.c;
            TO(b, n, c, m);//b倒向c
            if (mark[a][b][c] == false)//若该体积组尚未出现
            {
                mark[a][b][c] = true;//标记该体积组
                N tmp;
                tmp.a = a;
                tmp.b = b;
                tmp.c = c;
                tmp.t = now.t + 1;//生成新的状态
                if (a == s / 2 && b == s / 2)return tmp.t;
                if (c == s / 2 && b == s / 2)return tmp.t;
                if (a == s / 2 && c == s / 2)return tmp.t;//若该状态已经为平分状态,返回该状态耗时
                Q.push(tmp);//否则放入队列
            }
            a = now.a;
            b = now.b;
            c = now.c;
            TO(c, m, b, n);//c倒向b
            if (mark[a][b][c] == false)//若该体积组尚未出现
            {
                mark[a][b][c] = true;//标记该体积组
                N tmp;
                tmp.a = a;
                tmp.b = b;
                tmp.c = c;
                tmp.t = now.t + 1;//生成新的状态
                if (a == s / 2 && b == s / 2)return tmp.t;
                if (c == s / 2 && b == s / 2)return tmp.t;
                if (a == s / 2 && c == s / 2)return tmp.t;//若该状态已经为平分状态,返回该状态耗时
                Q.push(tmp);//否则放入队列
            }
        }
        return -1;
    }
    
    int main()
    {
        int s, n, m;
        while (scanf("%d%d%d", &s, &n, &m) != EOF)
        {
            if (s == 0)break;
            if (s % 2 == 1)//s为奇数则不能平分
            {
                puts("NO");
                continue;
            }
            for (int i = 0; i <= s; i++)
            {
                for (int j = 0; j <= s; j++)
                {
                    for (int k = 0; k <= s; k++)
                    {
                        mark[i][j][k] = false;
                    }
                }
            }
            N tmp;
            tmp.a = s;
            tmp.b = 0;
            tmp.c = 0;
            tmp.t = 0;//初始化状态
            while (Q.empty() == false)Q.pop();//清空上个队列的状态
            Q.push(tmp);//初始状态放入队列
            mark[s][0][0] = true;//标记初始状态
            int ans = BFS(s, n, m);
            if (ans == -1) puts("NO");
            else printf("%d
    ", ans);
        }
        return 0;
    }
    View Code

    总结广度优先搜索的几个关键字:

    1.状态。我们确定求解问题中的状态。通过状态的转移扩展,查找遍历所有 的状态,从而从中寻找我们需要的答案。

    2.状态扩展方式。在广度优先搜索中,我们总是尽可能扩展状态,并先扩展得出的状态先进行下一次扩展。在解答树上的变现为,我们按层次遍历所有状态。

    3.有效状态。对有些状态我们并不对其进行再一次扩展,而是直接舍弃它(剪枝)。 因为根据问题分析可知,目标状态不会由这些状态经过若干次扩展得到。即目标状态,不可能存在其在解答树上的子树上,所以直接舍弃。

    4.队列。为了实现先得出的状态先进行扩展,我们使用队列,将得到的状态依次放入队尾,每次取队头元素进行扩展。

    5.标记。为了判断哪些状态是有效的,哪些是无效的我们往往使用标记。

    6.有效状态数。问题中的有效状态数与算法的时间复杂度同数量级,所以在 进行搜索之前必须估算其是否在我们所可以接受的范围内。

    7.最优。广度优先搜索常被用来解决最优值问题,因为其搜索到的状态总是按照某个关键字递增(如前例中的时间和倒杯子次数),这个特性非常适合求解最优值问题。所以一旦问题中出现最少、最短、最优等关键字,我们就要考虑是否是广度优先搜索。

    深度优先搜索(DFS:depth-first search

    为了实现先得到的状态后得到扩展的效果,我们按理将使用堆栈保存和扩展搜索过程得到的状态,但是考虑到我们同样可以利用上节所提到的递归程序来实现这一功能,所以这里给出的深度优先遍历不使用堆栈而是使用递归程序。

    由于其缺少了广度搜索中按层次递增顺序遍历的特性。所以当深度优先搜索搜索到我们需要的状态时,其不再具有某种最优的特性。所以,在使用深度优先搜索时,我们更多的求解有或者没有的问题,即对解答树是否有我们需要的答案进行判定,而一般不使用深度优先搜索求解最优解问题。

    例 6.7 Temple of the bone

    题目描述:

    The doggie found a bone in an ancient maze, which fascinated him a lot. However, when he picked it up, the maze began to shake, and the doggie could feel the ground sinking. He realized that the bone was a trap, and he tried desperately to get out of this maze. The maze was a rectangle with sizes N by M. There was a door in the maze. At the beginning, the door was closed and it would open at the T-th second for a short period of time (less than 1 second). Therefore the doggie had to arrive at the door on exactly the T-th second. In every second, he could move one block to one of the upper, lower, left and right neighboring blocks. Once he entered a block, the ground of this block would start to sink and disappear in the next second. He could not stay at one block for more than one second, nor could he move into a visited block. Can the poor doggie survive? Please help him.

    输入:

    The input consists of multiple test cases. The first line of each test case contains three integers N, M, and T (1 < N, M < 7; 0 < T < 50), which denote the sizes of the maze and the time at which the door will open, respectively. The next N lines give the maze layout, with each line containing M characters. A character is one of the following:

    'X': a block of wall, which the doggie cannot enter; 

    'S': the start point of the doggie; 

    'D': the Door; or

    '.': an empty block.

    The input is terminated with three 0's. This test case is not to be processed. 

    输出:

    For each test case, print in one line "YES" if the doggie can survive, or "NO" otherwise.

    样例输入: 

    4 4 5 
    S.X. 
    ..X. 
    ..XD 
    .... 
    3 4 5 
    S.X. 
    ..X. 
    ...D 
    0 0 0 

    样例输出:

    NO
    YES

    解题代码:

    #include<cstdio>
    
    using namespace std;
    
    char maze[8][8];//保存地图信息
    int n, m, t;//地图大小为n*m,从起点到终点需要恰好t秒
    bool success;//是够找到所需状态的标记
    int go[][2] = { 1,0,-1,0,0,1,0,-1 };//四个方向的行走坐标
    void DFS(int x, int y, int time)//递归形式的深度优先搜索
    {
        for (int i = 0; i < 4; i++)
        {
            int nx = x + go[i][0];
            int ny = y + go[i][1];//计算其坐标
            if (nx<1 || nx>n || ny<1 || ny>m)continue;//坐标在地图之外
            if (maze[nx][ny] == 'X')continue;//是墙,跳过
            if (maze[nx][ny] == 'D')//该位置是门
            {
                if (time + 1 == t)//所用时间恰好为t
                {
                    success = true;//搜索成功
                    return;
                }
                else continue;
            }
            maze[nx][ny] = 'X';//该状态扩展而来的后续状态中,该位置不能被经过,直接修改为墙
            DFS(nx, ny, time + 1);//递归扩展该状态,所用时间递增
            maze[nx][ny] = '.';//后续状态全部遍历完毕,则退回上层状态,改为墙的位置重新改为普通位置
            if (success)return;//加入已经成功,停止搜索
        }
    }
    
    int main()
    {
        while (scanf("%d%d%d", &n, &m, &t) != EOF)
        {
            if (n == 0 && m == 0 && t == 0)break;
            for (int i = 1; i <= n; i++)
            {
                scanf("%s", maze[i] + 1);
            }
            success = false;//初始化成功标记
            int sx, sy;
            for (int i = 1; i <= n; i++)
            {
                for (int j = 1; j <= m; j++)
                {
                    if (maze[i][j] == 'D')
                    {
                        sx = i;
                        sy = j;
                    }
                }
            }
            for (int i = 1; i <= n; i++)
            {
                for (int j = 1; j <= m; j++)
                {
                    if (maze[i][j] == 'S'&&(i+j)%2==((sx+sy)%2+t%2)%2)
                    {
                        maze[i][j] = 'X';//起点标记为墙
                        DFS(i, j, 0);//递归扩展初始状态
                    }
                }
            }
            puts(success == true ? "YES" : "NO");//若success为真,输出yes
        }
        return 0;
    }
    View Code
  • 相关阅读:
    spring的常用配置
    aop切入点表达式
    代理模式
    hibernate的常用配置
    正则表达式
    Java配置
    性能提升
    创建vue3 项目
    manjaro
    单调队列
  • 原文地址:https://www.cnblogs.com/yun-an/p/11029331.html
Copyright © 2011-2022 走看看