贪吃蛇DFS补题(2.26更新极大值剪枝的拓展,防止TLE)

本文通过一个牛客网的贪吃蛇迷宫挑战,介绍了如何利用深度优先搜索(DFS)解决此类问题,并强调了在DFS中正确设置访问标记的重要性。同时,文章探讨了极大值剪枝的概念,以优化在大量数据下避免超时的策略,并提供了一个实例来解释剪枝算法的工作原理,展示了如何在分配玩具问题中应用此技术以达到最大喜爱值之和。

提示:今天下午打了牛客的最后一场比赛,其中贪吃蛇这道题困扰了我接近一小时,这道题说白了是个简单的DFS,套用模版就行了,但是不知道为啥就是过不了!

题目

链接:https://ac.nowcoder.com/acm/contest/9986/I
来源:牛客网

无限增长的贪吃蛇小游戏:

在一个n*m的迷宫中,有一条小蛇,地图中有很多围墙,猥琐的出题者用“#”表示,而可以走的路用“.”表示,小蛇他随机出生在一个点上,出生点表示为“S”,他想抵达的终点表示为“E”,小蛇有一个奇怪的能力,他每走一格便会增长一格,即他走了一格后,他的尾巴不会缩回。

小蛇想知道他怎么到达他想去的地方,请你帮助他。

PS:每格长1米,贪吃蛇规定不能撞墙,不能咬自己的身体。
输入描述:
第一行:输入N,M;

第二行:输入S的坐标Xs,Ys,E的坐标Xe,Ye;

后面的N行:

每行输入M个数,描述每一行的情况。

输出描述:
输出一个数,小蛇到达终点的最短距离(单位:cm),若无法达到,输出-1
示例1
输入
复制
3 3
1 1 3 3
.#.
.#.

输出
复制
400


提示:对于 100% 的数据:1\le n,m\le 1001≤n,m≤100 ,保证起点不是围墙。

二、AC代码

代码如下:

#include<bits/stdc++.h>
using namespace std;
int N,M;
int Xs,Ys,Xe,Ye;
int mi=0x3f3f3f3f;
char m[105][105];
int vis[105][105];
int t[4][2]={{0,-1},{0,1},{1,0},{-1,0}};
void dfs(int x,int y,int step){
	//弹出条件也就是找到终点
    if(x==Xe&&y==Ye){
        mi=min(mi,step);
    }
    for(int i=0;i<4;i++){
    	//四个方向的行走递归
        int xx=x+t[i][0];
        int yy=y+t[i][1];
        //确保这条路是能走的,而且没有跳出边界
        if(xx>=1&&xx<=N&&yy>=1&&yy<=M&&m[xx][yy]!='#'&&vis[xx][yy]==0){
        	//确保(xx,yy)这条路走和不走都能遍历
            vis[xx][yy]=1;
            dfs(xx,yy,step+1);
            vis[xx][yy]=0;
        }
    }
}

int main(){
    cin>>N>>M;
    cin>>Xs>>Ys>>Xe>>Ye;
    for(int i=1;i<=N;i++){
        for(int j=1;j<=M;j++){
            cin>>m[i][j];
        }
    }
    dfs(Xs,Ys,0);
    //如果找不到就说明mi没有找到更短的,直接输出-1
    if(mi==0x3f3f3f3f)cout<<-1;
    else cout<<mi*100;
	return 0;
}

总结

其实要注意的就是判断完后将当前vis标记为1,进行递归,然后将vis再调回0的状态

说实话,只要dfs会用,这道题不难


2.26小更新哈

关于极大值剪枝的拓展 介绍一下

我们先从题目引入:

蒜头君有 nn 个玩具要分给 n 个小朋友,每个小朋友对每个玩具都有一个喜爱值,第 i个小朋友对第 j 个玩具的喜爱值是 a[i][j]

现在蒜头君希望将玩具都分下去,但是希望小朋友们的喜爱值和可以最大,请你帮他计算一下,如何分配可以使得喜爱值之和达到最大?

输入格式
输入第一行包含一个正整数 n,表示有 n 个玩具和 n 个小朋友。

第二行至第 n+1 行共 n 行,每行有 n 个以空格分隔的正整数。第 i+1 行的第 j个数 k(1≤k≤1000),表示第 i 个小朋友对第 j 个玩具的喜爱值为k。

输出格式
输出只有一行,该行只有一个正整数,表示求得的喜爱值之和的最大值。

数据范围
对于 50% 的数据,1≤n≤9

对于 100% 的数据,1≤n≤17

输出时每行末尾的多余空格,不影响答案正确性

样例输入

3
10 6 8
9 2 3
1 7 2

样例输出

24


说明:这道题其实稍微读题就会发现是一道DFS可以解决的问题,唯一需要担心的是在最大值17^3的范围内可能会超时(其实一定会),这时候我们引入一个极大值剪枝的概念。

在输入时,求出了每一行的最大值,剪枝的关键就是如果曾经搜索到的最大值>=当前搜索到的值+还没搜索行的最大值之和(后缀最大值),就可以结束搜索了,因为在搜索下去,也不可能搜到比上一次大的(当前搜索的值+剩下的最大值<=上次搜索的最大值),极小值剪枝技巧也是类似。

光看文字可能有点烦有点难理解,接下来我们把上述例子做一个解释。
根据这个数据我们建立好图map
在这里插入图片描述
首先我们画出这个树
在这里插入图片描述
我们可以看到sum值最大是24,当然这是我们全部画出来的结果,那么根据这个算法,我们能有哪些改进呢?

让我们回忆一下,我们之前建立的dis数组的作用,我们发现我们把每行最大值相加存放在dis数组里,我们来看看里面的值(从0到3)【0,10,19,26】。其实这个dis数组就标记了还没搜索行的最大值之和。

比如说我们走到8-9-7这一行,我们当前ans值更新为了24,从上帝视角来看这是全图最大的情况,于是在走下一个dfs内嵌的时候,sum值在step=1的时候是8,此时我们判断(ans>=sum+dis[n]-dis[step-1])是否能成立。(dis[n]-dis[step-1]在当前情况是16,说明后边最大也只能大到16,当然实际是2+1=3,那么显然8+16==24这个值已经出现过了,后面的算法也就没有必要再执行下去了,于是直接return)

就这样,我们通过及时的跳出实现了缩短算法时间,防止了TLE
极小值剪枝同理

这里贴一下AC代码,便于复习

#include<bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
const int maxn = 1e6+5;
int mp[20][20],vis[30],m,n;
int dis[30],ans;
void dfs(int step,int sum)
{
    if(step==n+1) {
        ans = max(ans, sum);
    }
    if(ans>=sum+dis[n]-dis[step-1])
        return;

        for(int i=1;i<=n;i++)
        {
            if(!vis[i]) {
                vis[i] = 1;
                dfs(step + 1, sum + mp[step][i]);
                vis[i] = 0;
            }

        }

}
int main(){


    while(~scanf("%d",&n))
    {
        memset(mp,0,sizeof mp);
        memset(vis,0,sizeof vis);
        memset(dis,0,sizeof dis);
        ans = -1;
        for(int i=1;i<=n;i++) {
            m = -1;
            for (int j = 1; j <= n; j++) {
                scanf("%d", &mp[i][j]);
                m = max(m, mp[i][j]);
            }
            dis[i] = dis[i-1] + m;
        }
        dfs(1,0);
        cout << ans << endl;
    }
    return 0;
}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值