OpenJudge 1804《小游戏》题解报告

一、题目链接

屠龙宝刀点击就送

解法一:广度优先搜索

(一)思路

本题的路线可延伸到矩形板的外面,延伸多远都没关系。但是因为题目要求的是最小的线段数量,则伸出一个格子跟伸出多个格子没有区别。为了简单起见,伸出一个格子就好。这样就可以把原来的矩形板的面积增加一圈,即列从[1, w]扩大为[0, w+1],行从[1, h]扩大为[0, h+1]。

本题可使用广度优先搜索来解决。每个head格子出队后,周围最多有四个不同方向的相邻格子可入队。当某个方向的相邻格子入队后,优先往这个方向一直前进,在这个过程中不需要拐弯,经过的格子也不需要入队,因为入队会导致拐弯次数增加。

(二)代码

#include <iostream>
#include <cstdio>
#include <queue>
#include <memory.h>
using namespace std;

struct grid
{
    int x;      //本题中x为列
    int y;      //y为行
    int step;   //第几条线段,即变了几次方向
};

int dx[]={0, 0, 0, -1, 1};//移动列
int dy[]={0, -1, 1, 0, 0};//移动行
int sx, sy, ex, ey, w, h;

const int maxN = 80;
char Map[maxN][maxN];
bool vis[maxN][maxN];

bool check(int x, int y)
{
    //因为可跑到矩形板的外面,所以四个边界都要加1
    if(x >= 0 && x <= w+1 && y >= 0 && y <= h+1 && !vis[x][y])
    {
        if(Map[x][y] == ' ' || (Map[x][y] == 'X' && x == ex && y == ey))
        {
            return true;
        }

        return false;
    }

    return false;
}

queue<grid>q;

void bfs()
{
    grid start;
    start.x = sx;
    start.y = sy;
    start.step = 0;

    vis[sx][sy] = true;
    q.push(start);

    while(!q.empty())
    {
        grid head = q.front();
        q.pop();
        grid Next; //相邻的四个格子
        for(int i=1; i<=4; i++)
        {
            Next.x = head.x + dx[i];
            Next.y = head.y + dy[i];
            Next.step = head.step + 1; //每换个方向,线段数就会加1,所以step加1
            while(check(Next.x, Next.y))
            {
                if(Next.x == ex && Next.y == ey)
                {
                    printf("%d segments.\n", Next.step);
                    return;
                }

                vis[Next.x][Next.y] = true;
                q.push(Next);

                //往dx[i],dy[i]这个固定的方向一直前进,不会引起线段数量增加,所以step不加1
                //处于固定方向一直前进中途上的各个点,不需要进队列,因为进队列就意味着可调方向,
                //调方向得到的线段数必然多于不调方向得到的线段树。
                Next.x += dx[i];
                Next.y += dy[i];
            }
        }
    }

    printf("impossible.\n");

    return;
}

int main()
{
    //freopen("game.in", "r", stdin);

    int cnt=0;

    while(true)
    {
        cnt++;
        scanf("%d%d", &w, &h); //宽(列)和高(行)
        if(w==0 && h==0)
        {
            break;
        }

        memset(Map, ' ', sizeof(Map));

        getchar(); //吸收换行符

        for(int r = 1; r <= h; r++) //行
        {
            string str;
            getline(cin, str);
            for (int c = 1; c <= w; c++)//列
            {
                Map[c][r] = str[c - 1];
            }
        }

        printf("Board #%d:\n", cnt);
        int pairCnt = 0;

        while(true)
        {
            pairCnt++;
            scanf("%d%d%d%d", &sx, &sy, &ex, &ey);
            if(sx==0 && sy==0 && ex==0 && ey==0)
            {
                break;
            }

            printf("Pair %d: ", pairCnt);
            memset(vis, false, sizeof(vis));

            bfs();

            //注意queue没有clear()函数,要清空得逐个弹出
            while(!q.empty())
            {
                q.pop();
            }
        }
        printf("\n");
    }

    return 0;
}

解法二:深度优先搜索

(一)思路

本题也可以使用深搜来解决。对于每个格子,都有四种前进的方向。如果相邻两个格子的前进方向一致,则线段总数不变,如果方向不一样,则线段总数加1。

在使用深搜时,因为用到了递归,所以最先算出的是终点到终点的线段数量(不用挪动,线段数量为0),再往回计算终点旁边的点到终点的线段数量,再往回推算与终点距离为2的点到终点的线段数量,……,最后计算出起点到终点的线段数量。
在这里插入图片描述
如上图所示,假设D是终点,则D到D的线段数量为0。C到D的线段数量为1。A到C的方向与C到D的方向不一样,所以线段数量为2。B到C的方向与C到D的方向一样,则B到D的线段数量为1。

(二)代码

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

int w, h;
int dir[8] = {0, -1, 1, 0, 0, 1, -1, 0}; //每两个数对应一个移动方向,顺序为上、右、下、左
const int SIZE = 80;
bool board[SIZE][SIZE];	    //存储棋盘,有卡片为true,空为false
int seg[SIZE][SIZE][4];     //存储从点(curX,curY)开始,第一步方向是r,最少需要多少条线段到达目标点
const int INF = 99999999;   //因为为+1操作,不能用INT_MAX
int startX, startY, endX, endY; //起点坐标和终点坐标

int dfs(int curX, int curY, int d)  //d是direction的缩写,表示方向
{
    //记忆化存储,已经计算过就不用再次计算了
    if (seg[curX][curY][d])
    {
        return seg[curX][curY][d];
    }

    //终点到终点不用走,需要0条线段
    if (curX == endX && curY == endY)
    {
        for (int i = 0; i < 4; i++)
        {
            seg[curX][curY][i] = 0;
        }

        return 0;
    }

    //带卡片的格子走不到终点,用INF来表示
    if (board[curY][curX])
    {
        for (int i = 0; i < 4; i++) //四个方向
        {
            seg[curX][curY][i] = INF;
        }

        return INF;
    }

    seg[curX][curY][d] = INF;
    int nextX = curX + dir[2 * d];
    int nextY = curY + dir[2 * d + 1];
    if (nextX >= 0 && nextX <= w + 1 && nextY >= 0 && nextY <= h + 1)
    {
        for (int i = 0; i < 4; i++)//四个方向
        {
            seg[nextX][nextY][i] = dfs(nextX, nextY, i);
            if (i == d) //前进方向不变,到下一个节点的线段数量不变
            {
                seg[curX][curY][d] = min(seg[nextX][nextY][i], seg[curX][curY][d]);
            }
            else        //前进方向改变,到下一个节点的线段数量+1
            {
                seg[curX][curY][d] = min(seg[nextX][nextY][i] + 1, seg[curX][curY][d]);
            }
        }
    }

    return seg[curX][curY][d];
}

int main()
{
    //freopen("game.in", "r", stdin);

    int T = 1; //第几组数据
    while (cin >> w >> h)
    {
        if (w == 0 && h == 0)
        {
            break;  //所有输入都结束了
        }

        memset(board, false, sizeof(board)); //初始化所有格子没有卡片

        for (int i = 1; i <= h; i++)    //行
        {
            cin.get();  //读取空格或回车或制表符

            char c;
            for (int j = 1; j <= w; j++) //列
            {
                cin.get(c);
                if (c == 'X')
                {
                    board[i][j] = true;    //有卡片
                }
            }
        }

        cout << "Board #" << T << ":" << endl;

        int group = 1;  //第几组数据
        while (cin >> startX >> startY >> endX >> endY)
        {
            if ((startX | endX | startY | endY) == 0)
            {
                break; //四个0表示本组测试结束
            }

            memset(seg, 0, sizeof(seg));

            int ans = INF;
            for (int i = 0; i < 4; i++)//四个方向
            {
                int nextX = startX + dir[2 * i];
                int nextY = startY + dir[2 * i + 1];
                int cnt = INF;
                for (int j = 0; j < 4; j++)//四个方向
                {
                    int temp = dfs(nextX, nextY, j);
                    if (i == j) //方向相同说明没有拐弯,线段数量不变
                    {
                        cnt = min(cnt, temp);
                    }
                    else        //拐弯,线段数量增加
                    {
                        cnt = min(cnt, temp + 1);
                    }
                }
                ans = min(ans, cnt);
            }
            ans += 1; //在计算的过程中实际上差了1,因为第一次挪动时没有算

            cout << "Pair " << group << ": ";
            group++;

            if (ans >= INF)
            {
                cout << "impossible." << endl;
            }
            else
            {
                cout << ans << " segments." << endl;
            }
        }//内层while结束

        cout << endl;
        T++; //下一轮游戏
    }//外层while结束

    return 0;
}

了解信息学奥赛请加微信307591841(QQ同号)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值