初学算法----深度优先搜索地图型题目

Maze

描述:

Acm, a treasure-explorer, is exploring again. This time he is in a special maze, in which there are some doors (at most 5 doors, represented by 'A', 'B', 'C', 'D', 'E' respectively). In order to find the treasure, Acm may need to open doors. However, to open a door he needs to find all the door's keys (at least one) in the maze first. For example, if there are 3 keys of Door A, to open the door he should find all the 3 keys first (that's three 'a's which denote the keys of 'A' in the maze). Now make a program to tell Acm whether he can find the treasure or not. Notice that Acm can only go up, down, left and right in the maze.

输入:

The input consists of multiple test cases. The first line of each test case contains two integers M and N (1 < N, M < 20), which denote the size of the maze. The next M lines give the maze layout, with each line containing N characters. A character is one of the following: 'X' (a block of wall, which the explorer cannot enter), '.' (an empty block), 'S' (the start point of Acm), 'G' (the position of treasure), 'A', 'B', 'C', 'D', 'E' (the doors), 'a', 'b', 'c', 'd', 'e' (the keys of the doors). The input is terminated with two 0's. This test case should not be processed.

输出:

For each test case, in one line output "YES" if Acm can find the treasure, or "NO" otherwise.

样例输入:

4 4 
S.X. 
a.X. 
..XG 
.... 
3 4 
S.Xa 
.aXB 
b.AG 
0 0

样例输出:

YES 
NO

1.关于找到结果就回归这件事:
像这种在图上遍历找某个确定的点,找到点后,可以不用再找了,直接退出整个遍历过程输出即可,
那么为了使达到这种效果,我们就要设置一点套路让他去能够一找到就退出整个过程

bool dfs (int y,int x)
{
if (maper[y][x]=='G')
{
return true;
}
if (maper[y][x]=='X' || x<1 || y<1 || x>c || y>r || vis[y][x]==1)
{
return false;
}
if (maper[y][x]>='a' && maper[y][x]<='e')
{
keyer[maper[y][x]-'a']++;
}
if (maper[y][x]>='A' && maper[y][x]<='E')
{
if (keyer[maper[y][x]-'A']!=needkey[maper[y][x]-'A'])
{
return false;
}
}
vis[y][x]=1;
for (int i=0;i<4;i++)
{
int ny=y+dy[i],nx=x+dx[i];
if (dfs(ny,nx))//看这部操作,如果我找到了,深度优先搜索返回正确,那一定是一直返回到最终,就可以达到那种效果了
{
return true;
}
}
vis[y][x]=0;
return false;
}

当然这种操作有很多方式,根据具体情况具体设置

2.关于如何回溯与判断条件写哪里这件事:

1.bool dfs(int y, int x, int step)
{
    if (step == r * c)
    {
        return true;
    }
    for (int i = 0; i < 8; i++)
    {
        int ny = y + dy[i];
        int nx = x + dx[i];
        if (nx < 1 || ny < 1 || nx > c || ny > r || vis[ny][nx] == 1)
        {
            continue;
        }
        else
        {
            vis[ny][nx] = 1;
            step++;
            maper[step].row = ny;
            maper[step].col = nx;
            if (dfs(ny, nx, step))
            {
                return true;
            }
            step--;
            vis[ny][nx] = 0;
        }
    }
    return false;
}
2.

bool dfs (int y,int x)
{
if (maper[y][x]=='G')
{
return true;
}
if (maper[y][x]=='X' || x<1 || y<1 || x>c || y>r || vis[y][x]==1)
{
return false;
}
if (maper[y][x]>='a' && maper[y][x]<='e')
{
keyer[maper[y][x]-'a']++;
}
if (maper[y][x]>='A' && maper[y][x]<='E')
{
if (keyer[maper[y][x]-'A']!=needkey[maper[y][x]-'A'])
{
return false;
}
}
vis[y][x]=1;
for (int i=0;i<4;i++)
{
int ny=y+dy[i],nx=x+dx[i];
if (dfs(ny,nx))
{
return true;
}
}
vis[y][x]=0;
return false;
}

我这两段代码判断条件放的位置不一样

他们其实都是同一个道理

接下来是上述题目了:

#include <bits/stdc++.h>
using namespace std;

char maper[25][25];
int r, c;
int keyer[5], needkey[5], everhave[5];
int vis[25][25], dy[] = {-1, 0, 1, 0}, dx[] = {0, 1, 0, -1};

bool dfs(int y, int x)
{
    if (maper[y][x] == 'G')
    {
        return true;
    }
    for (int i = 0; i < 4; i++)
    {
        int ny = y + dy[i], nx = x + dx[i];
        if (maper[ny][nx] == 'X' || nx < 1 || ny < 1 || nx > c || ny > r || vis[ny][nx] == 1)
        {
            continue;
        }
        else if (maper[ny][nx] >= 'A' && maper[ny][nx] <= 'E')
        {
            if (keyer[maper[ny][nx] - 'A'] != needkey[maper[ny][nx] - 'A'])
            {
                continue;
            }
        }
        else if (maper[ny][nx] >= 'a' && maper[ny][nx] <= 'e' && everhave[maper[ny][nx] - 'a'] == 0)
        {
            keyer[maper[ny][nx] - 'a']++;
            everhave[maper[ny][nx] - 'a'] = 1;
        }
        vis[ny][nx] = 1;
        if (dfs(ny, nx))
        {
            return true;
        }
        vis[ny][nx] = 0;
    }
    return false;
}

int main()
{
    while (1)
    {
        cin >> r >> c;
        if (r == 0 && c == 0)
        {
            break;
        }
        else
        {
            int sx, sy;
            for (int i = 1; i <= r; i++)
            {
                for (int j = 1; j <= c; j++)
                {
                    cin >> maper[i][j];
                    if (maper[i][j] == 'S')
                    {
                        sy = i;
                        sx = j;
                    }
                    else if ('a' <= maper[i][j] && 'e' >= maper[i][j])
                    {
                        needkey[maper[i][j] - 'a']++;
                    }
                }
            }
            memset(vis, 0, sizeof(vis));
            memset(keyer, 0, sizeof(keyer));
            memset(needkey, 0, sizeof(needkey));
            memset(everhave, 0, sizeof(everhave));
            vis[sy][sx] = 1;
            if (dfs(sy, sx))
            {
                printf("YES\n");
            }
            else
            {
                printf("NO\n");
            }
        }
    }
    return 0;
}
次林梦叶 21:54:57
我这段代码肯定没错,但是一定超时,为什么?因为回溯实在是太多了。
想想回溯的作用是什么?是防止退回后,再次搜索时,本应该要到达的点,因为上一次没有回溯而到不了

次林梦叶 21:55:43
这里走迷宫,会超时是因为走太多次相同的点了

次林梦叶 21:55:53
如果我想每个点我只走一次

次林梦叶 21:56:05
那我就应该不回溯

次林梦叶 21:56:28
不回溯的结果就是我不能重复走点了

次林梦叶 21:56:34
但是依旧能够回退

次林梦叶 21:56:38
选择能走的点走

次林梦叶 21:56:46
这样就可以大大减少时间了

来看下大佬的写法:
#include<iostream>
#include<cstring>
using namespace std;
char a[30][30];int c[30][30],d[30][30];
int b[5],bb[5],cx[4]={1,-1},cy[4]={0,0,1,-1};
int m,n,x1,y1,f;
void ys(int x,int y)
{
if(f)return;//看这里,大佬找到结果后立马退出整个过程的方法
for(int i=0;i<4;i++)
{
int x2=x+cx[i],y2=y+cy[i];
if(a[x2][y2]=='G')//如果找钥匙时找到宝藏,做标记,然后直接结束所有过程。
{
f=1;
return;
}
if(a[x2][y2]>='a'&&a[x2][y2]<='e')
{
b[a[x2][y2]-'a']--;//每找到一个钥匙,对应的b数组自减,值为0时代表全部集齐
a[x2][y2]='.';//将被找到的钥匙标记为空地
}
if(a[x2][y2]=='.'&&!c[x2][y2])
{
c[x2][y2]=1; //标记找钥匙时走过的路
ys(x2,y2);
}//由于找钥匙时只要把所有路径走一遍,所以不需要回溯。
}
}

void ljq(int x,int y)
{
if(f)return;//找到宝藏后直接结束
for(int i=0;i<4;i++)
{
int x2=x+cx[i],y2=y+cy[i];
if(a[x2][y2]>='A'&&a[x2][y2]<='E'&&!b[a[x2][y2]-'A']&&bb[a[x2][y2]-'A'])
{ //找到门后,如果已经集齐全部钥匙,则把门打开
a[x2][y2]='.';//将被打开的门标记为空地
memset(d,0,sizeof(d));/*d数组纪录找门时走过的路,由于会找到新的钥匙,可能原来没打
开的门可以打开,所以将d数组清零。*/
ys(x2,y2);//每打开一个门,从被打开门的位置开始找钥匙。
}

if(a[x2][y2]=='.'&&!d[x2][y2])
{
d[x2][y2]=1;
ljq(x2,y2);
}//同上 ,不需要回溯。
}
}
int main()
{
cin>>m>>n;
while(m)
{
f=0;
memset(a,0,sizeof(a));
memset(c,0,sizeof(c));
memset(d,0,sizeof(d));
memset(b,0,sizeof(b));
memset(bb,0,sizeof(bb));
for(int i=1;i<=m;i++)
for(int j=1;j<=n;j++)
{
cin>>a[i][j];
if(a[i][j]=='S')
{
x1=i;y1=j;
a[i][j]='.';
}
if(a[i][j]>='a'&&a[i][j]<='e')
{
b[a[i][j]-'a']++;
bb[a[i][j]-'a']=1;
}//b数组纪录每种钥匙的数目,bb数组纪录有没有出现这种钥匙,0下标代表a,依此类推

}
ys(x1,y1);
/*次林梦叶 22:00:31
先在能走的地方把钥匙都找完

次林梦叶 22:00:58
而不是边找钥匙找到钥匙就立马去开门

次林梦叶 22:01:00
那样会很麻烦

次林梦叶 22:01:34
然后看一下能不能开门,如果开不了门就一定找不到宝藏*/
ljq(x1,y1);
if(f)cout<<"YES"<<endl;
else cout<<"NO"<<endl;
cin>>m>>n;
}
}
 
posted @ 2022-01-20 22:05  次林梦叶  阅读(59)  评论(0)    收藏  举报