ACM新手DAY 4 DFS及其应用

本文通过几个具体的例子,介绍了如何使用深度优先搜索(DFS)算法来解决迷宫寻路、中国象棋马走日问题、素数环问题等经典问题。文章详细解析了DFS算法的应用场景和实现细节,帮助读者掌握该算法的精髓。

题解

A - 迷宫问题

题目:定义一个二维数组:
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表示可以走的路,只能横着走或竖着走,不能斜着走,要求编程序找出从左上角到右下角的最短路线。

  • DFS解决迷宫问题
  • 直接代码分析
#include <iostream>
#include <vector>
#include <string.h>
using namespace std;

int m(5),n(5);
int vis[20][20];//标记数组,去掉已经经过的地点
int f[4][2] = {{0,-1},{-1,0},{0,1},{1,0}};//方向数组,用在选择方向时的坐标处理
int a[20][20];
vector<pair<int,int> > q, qq;//和结构体有些类似,头文件<vector>;第一个q是队列,第二个用来储存最短路径的队列

int xb(0),yb(0),xe(4),ye(4);//起始坐标与终点坐标
int minn(100);

bool judge(int x1,int y1)//判断下一个方向的坐标是否合法
{
    return (x1>=0&&x1<m&&y1>=0&&y1<n&&vis[x1][y1]==0&&a[x1][y1]==0);
}
void dfs(int x,int y, int ss)//!!!这里的ss是对每一条路径长度记录用的
{
    if(x == xe&&y== ye)
    {
        if(ss < minn)
        {
            minn = ss;
            qq = q;
        }
        return ;
    }
    for(int i=0; i<4; i++)
    {
        int x1 = x+f[i][0];
        int y1 = y+f[i][1];
        if(judge(x1,y1))
        {
            vis[x1][y1]=1;
            pair<int,int> z(x1,y1);
            q.push_back(z);//这一句和上一句是用来把新的坐标放进队列
            dfs(x1,y1,ss+1);
            vis[x1][y1]=0;//标记回溯
            q.pop_back();//队列回溯
        }
    }
}
int main()
{
    memset(a,0,sizeof a);
    memset(vis,0,sizeof vis);//数组初始化,头文件<string.h>

    for(int i=0; i<5; i++)
        for(int j=0; j<5; j++)
            cin>>a[i][j];
            
    vis[xb][yb]=1;
    pair<int,int> z(xb,yb);
    q.push_back(z);
    dfs(xb,yb, 1);
    for(int i=0; i<qq.size(); i++)
    {
        cout<<"("<<qq[i].first<<", "<<qq[i].second<<")\n";
    }
    return 0;
}

B - 马走日

题目:马在中国象棋以日字形规则移动。编写一段程序,给定n*m大小的棋盘,以及马的初始位置(x,y),要求不能重复经过棋盘上的同一个点,计算马可以有多少途径遍历棋盘上的所有点。

  • DFS遍历所有点
  • 走向下一点的方向从四个变成了八个,DFS里面的判断和标记也有所变化(遍历所有点与从某一点到另一点的区别)
  • 方向数组
int f[8][2]=
{
    {1,2},{1,-2},{-1,2},{-1,-2},{2,1},{2,-1},{-2,1},{-2,-1}
};
  • DFS函数
void dfs(int a,int b,int ss)//ss依旧记录路径长度(或者说坐标点数量)
{
    if(ss==n*m)//当一条路径经过的坐标点数量达到棋盘上点的数量时,记录路径数目的s加一
        s++;
    if(map[a][b]==0)
    {
        map[a][b]=1;//标记数组
        for(int i=0; i<8; i++)
        {
            int newx=a+f[i][0];
            int newy=b+f[i][1];
            if(newx<0||newx>=n||newy<0||newy>=m||map[newx][newy]==1)//判断下一个点是否合法的部分
                continue;
            dfs(newx,newy,ss+1);
        }
        map[a][b]=0;
    }
    return ;
}

C - Prime Ring Problem

题目:环由n个圆组成,如图所示。将自然数1,2,…,n分别放入每个圆中,两个相邻圆中的数字之和应为素数。
在这里插入图片描述

  • DFS遍历所有情况
  • 注意点:1.第一个圆的数量应始终为1。2.不要忘了最后一个数还要和第一个数放一起判断一下。
  • 这里有一种方法就是把0~n的数字用0/1数组判断是不是素数。没太大必要,算是尝试新东西。
#include<iostream>
#include<cmath>
#include<string.h>
using namespace std;

int a[100], vis[100];
int n,prime[100];//直接prime把数字弄成01组,减少判断函数的调用
bool isPrime(int x)
{
    for (int i=2; i<=sqrt(x+0.0); i++)
    {
        if (x%i==0)
            return false;
    }
    return true;
}
void dfs(int x)
{
    if (x==n&&prime[a[1]+a[n]])//最后一个数与第一个数的判断
    {
        for(int i=1; i<n; i++)
            cout<<a[i]<<" ";
        cout<<a[n]<<endl;
        return ;
    }
    else
    {
        for(int i=2; i<=n; i++)
        {
            if (!vis[i]&&prime[a[x]+i])//m是假的,n是真的才成立
            {
                a[x+1]=i;
                vis[i]=1;
                dfs(x+1);
                vis[i]=0;
            }
        }
    }
    return ;
}
int main()
{
    int t(0);
    while(cin >> n)
    {
        t++;
        for(int i=2; i<=n*2; i++)
            prime[i]=isPrime(i);
        cout<<"Case "<<t<<":"<<endl;
        memset(vis,0,sizeof(vis));
        a[1] = 1;
        dfs(1);
        cout<<endl;
    }
    return 0;
}

D - Sticks

题目:取相同长度的棍子若干并随意切割,直到所有部分最多变成50个单位。现在想把棍子归还原来的状态,但是不知道原来有多少棍子和原来多长。计算这些棒可能的最小原始长度。

  • DFS+剪枝
  • 都标记在代码里了,很详细,剪枝方法有参考别人
#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;
int n;
int maxx, sum;
int temp;
int a[100];
int vis[100];

int dfs(int cnt, int index, int ss)
{
    if (cnt == n)//结束点,当棒棒的数目等于题目中的n
        return 1;
    for (int i = index ; i < n; i++)
    {
        if (vis[i])continue;//标记点呐。。

        if (ss + a[i] < temp)
        {
            vis[i] = 1;
            if (dfs(cnt + 1, i + 1, ss + a[i]))//不能组成temp的话就加一继续组
                return 1;
            vis[i] = 0;

            while(a[i] == a[i + 1] && i + 1 < n) i++;//这一根都不能实现其他的等长也不能实现。。。为什么还超时
        }
        else if (ss + a[i] == temp)//做成一个temp就要重新组了
        {
            vis[i] = 1;
            if (dfs(cnt + 1, 0, 0))
                return 1;
            vis[i] = 0;

            return 0;//如果当前木棒与后面所有木棒都拼不上,直接返回false;
        }
        if(ss == 0)//这一根都没用上自然结束了
            return 0;
    }
    return 0;
}
int cmp(int a, int b)
{
    return a > b;
}
int main()
{
    while (cin >> n&&n)
    {
        maxx = 0;
        sum = 0;
        memset(a, 0, sizeof a);
        memset(vis, 0, sizeof vis);
        for (int i = 0; i < n; i++)
        {
            cin >> a[i];
            sum += a[i];//得到总长
            maxx = max(maxx, a[i]);//这对棒子里最长的棒棒
        }
        sort(a , a + n , cmp);//之后,a[i]都是降序的了,从大的开始找也是为了减少时间
        for (int i = n; i > 0; i--)//从n开始尝试,因为n越大,单个小棒越小
        {
            temp = sum/i; //temp指的是可能的长度
            if (sum % i != 0 || temp < maxx)continue;//筛选:不能被整除或者单个长度小于maxx
            else
            {
                memset(vis, 0, sizeof vis);
                if (dfs(0, 0, 0))
                { cout << temp << endl; break; }
            }
        }
    }
    return 0;
}

F - Tempter of the Bone

题目:迷宫是一个大小为N×M的矩形。迷宫中有一扇门。在开始时,门被关闭,它将在第T秒开启一小段时间(不到1秒)。因此,小狗必须在第T秒才到达门口。在每一秒中,他可以将一个块移动到上,下,左和右相邻块之一。一旦他进入一个区块,这个区块的地面将开始下沉并在下一秒消失。他不能在一个街区停留一秒以上,也不能进入一个被访问的街区。判断小狗能否活下来?

  • 有条件的走迷宫问题
  • DFS+剪枝
  • DFS部分将前几题的稍微改动就行,难点还在剪枝。
#include<iostream>
#include<cstring>
using namespace std;
int n, m;
int t;
int xe, ye, xd, yd;
bool flag = false;
char a[8][8];
int vis[8][8];
int nex[4][2] = { {1,0},{0,-1},{0,1},{-1,0} };

void dfs(int x, int y, int cnt)
{
    if (flag)  return;//也试了几种剪枝的思路,还不如这一句有用
    if (x == xe && y == ye)
    {
        if (cnt == t) flag = true;
        return;
    }
    for (int i = 0; i < 4; i++)
    {
        int tx = x + nex[i][0];
        int ty = y + nex[i][1];
        if (tx > m || tx<1 || ty>n || ty < 1 ||vis[tx][ty] == 1)
            continue;
        else
        {
            if (vis[tx][ty] == 0 && a[tx][ty] == '.' || a[tx][ty] == 'D')
            {
                vis[tx][ty] = 1;
                dfs(tx, ty, cnt + 1);
                vis[tx][ty] = 0;
            }
        }
    }
    return;
}
int main()
{
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);//又见到了
    while (cin >> m >> n >> t && m && n && t)
    {
        memset(vis, 0, sizeof vis);
        for (int i = 1; i <= m; i++)
            for (int j = 1; j <= n; j++)
            {
                cin >> a[i][j];
                if (a[i][j] == 'D')
                { xe = i; ye = j; }
                if (a[i][j] == 'S')
                { xd = i; yd = j; }
            }
        vis[xd][yd] = 1;
        flag = false;
        dfs(xd, yd, 0);
        if (flag)
        {
            cout << "YES" << endl;
        }
        else
            cout << "NO" << endl;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值