题目1335:闯迷宫(40分)

本文探讨解决迷宫寻径问题,重点在于计算从起点到终点的最短路径,考虑玩家可以向四个方向移动。通过初始化距离矩阵、逐次更新路径成本,最终求解最优路径。文中提供了三种不同的算法实现方式,包括基于状态转移的DP方法、Fast Sweeping算法及深度优先搜索(DFS)等。每种方法都针对不同场景进行了优化,以求在效率和准确性之间取得平衡。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

http://ac.jobdu.com/problem.php?id=1335


题目描述:

sun所在学校每年都要举行电脑节,今年电脑节有一个新的趣味比赛项目叫做闯迷宫。
sun的室友在帮电脑节设计迷宫,所以室友就请sun帮忙计算下走出迷宫的最少步数。
知道了最少步数就可以辅助控制比赛难度以及去掉一些没有路径到达终点的map。
比赛规则是:从原点(0,0)开始走到终点(n-1,n-1),只能上下左右4个方向走,只能在给定的矩阵里走。

输入:

输入有多组数据。
每组数据输入n(0<n<=100),然后输入n*n的01矩阵,0代表该格子没有障碍,为1表示有障碍物。
注意:如果输入中的原点和终点为1则这个迷宫是不可达的。

输出:

对每组输入输出该迷宫的最短步数,若不能到达则输出-1。

样例输入:
2
0 1
0 0
5
0 0 0 0 0
1 0 1 0 1
0 0 0 0 0
0 1 1 1 0
1 0 1 0 0
样例输出:
2
8
一开始没认真看题目,以为是最简单的只能向下和向右两个方向走,于是简单的打表递推提交通过了10个测试用例里的4个,看到题目中的是可以向四个方向移动的,既然一开始考虑使用状态转移就坚持一条道走到黑了,这里存在的问题是不满足dp里的无后效性,不太好找到一个合适的方向进行计算,因为当前节点的值会依赖4个相邻的结点,似乎一趟扫描基本不太可能,不过最近实现一篇论文的时候用到了  fast sweeping算法,具体的思想也是从一些确定点source的效果逐级扩散到其他点(这里本质上很相似,而且不用记录到达每一个状态的细节信息),一开始简单的表递推的时候从四个方向扫描,最后再加上一趟从左上到右下的扫描结果通过10个里面的9个(其实只是随便试一试,明显感觉算法在逻辑上还是有问题的(因为更新的时候始终只考虑两个相邻的两个点),说明测试用例并不是太强),以下是代码:     写到后面才发现代码中有致命问题的, 因为下标为0的两列值再初始确定后就固定了不在更新了,囧

#include <iostream>
#include <iomanip>
#include <cmath>
//#include <map>
#include <assert.h>
#include <cstring>
#include <string>
#include <vector>
#include <set>
#include <algorithm>
using namespace std;
 
const int MAXDIST = 10000;
int map[105][105];
int dist[105][105];
void InitDist( int n)
{
     for ( int i=0; i<=n; i++) //多取一位
         for ( int j=0; j<=n; j++)
             dist[i][j]=MAXDIST;
}
int computePath( int n)
{
     if (map[0][0]==1 || map[n-1][n-1]==1)
         return -1;
     InitDist(n);
     dist[0][0]=0;
     for ( int i=1; i<n; i++)
         if (0 == map[0][i])
             dist[0][i]=dist[0][i-1]+1;
     
     for ( int i=1; i<n; i++)
         if (0==map[i][0])
             dist[i][0]=dist[i-1][0]+1;
 
 
     for ( int i=1; i<n; i++)
         for ( int j=1; j<n; j++) //红色部分为问题所在,后面类似
         {
             if (0 == map[i][j])
             {
                 int temp1 = dist[i-1][j]+1;
                 int temp2 = dist[i][j-1]+1;
                 int temp = temp1<temp2 ? temp1:temp2;
                 if (temp<dist[i][j])
                     dist[i][j]=temp;
             }  
         }
 
     for ( int i=1; i<n; i++)
         for ( int j=n-1; j>0; j--)
             if (0 == map[i][j])
             {
                 int temp1 = dist[i-1][j]+1;
                 int temp2 = dist[i][j+1]+1;
                 int temp = temp1<temp2 ? temp1:temp2;
                 if (temp<dist[i][j])
                     dist[i][j]=temp;
             }
 
     for ( int i=n-1; i>0; i--)
         for ( int j=1; j<n; j++)
             if (0 == map[i][j])
             {
                 int temp1 = dist[i+1][j]+1;
                 int temp2 = dist[i][j-1]+1;
                 int temp = temp1<temp2 ? temp1:temp2;
                 if (temp<dist[i][j])
                     dist[i][j]=temp;
             }
 
     for ( int i=n-1; i>0; i--)
         for ( int j=n-1; j>0; j--)
             if (0 == map[i][j])
             {
                 int temp1 = dist[i+1][j]+1;
                 int temp2 = dist[i][j+1]+1;
                 int temp = temp1<temp2 ? temp1:temp2;
                 if (temp<dist[i][j])
                     dist[i][j]=temp;
             }
 
     for ( int i=1; i<n; i++)
         for ( int j=1; j<n; j++)
         {
             if (0 == map[i][j])
             {
                 int temp1 = dist[i-1][j]+1;
                 int temp2 = dist[i][j-1]+1;
                 int temp = temp1<temp2 ? temp1:temp2;
                 if (temp<dist[i][j])
                     dist[i][j]=temp;
             }  
         }
     if (dist[n-1][n-1]>=MAXDIST)
         return -1;
     else
         return dist[n-1][n-1];
}
int main()
{
     //freopen("in.txt","r",stdin);
     int n;
     while (cin>>n)
     {
         for ( int i=0; i<n; i++)
             for ( int j=0; j<n; j++)
                 cin>>map[i][j];
         cout<<computePath(n)<<endl;
     }
     return 0;
}



纠正上面提到的问题果然AC了, 没有第五趟额外的扫描代码通过不了,所以暂时还不确定代码绝对正确,第五趟是否一定不会再有值更新并无把握,以后再仔细考虑吧,不过后面写的直到完全没值更新了才停止扫描(只朝一个方向扫描只为代码简洁,收敛速度会慢一些吧)160ms-》190ms
#include <stdio.h>
#include <iostream>
#include <iomanip>
#include <cmath>
//#include <map>
#include <assert.h>
#include <cstring>
#include <string>
#include <vector>
#include <set>
#include <algorithm>
using namespace std;
  
const int MAXDIST = 10000;
int map[105][105];
int dist[105][105];
void InitDist( int n)
{
     for ( int i=0; i<=n; i++) //多取一位 从0取才有意义
         for ( int j=0; j<=n; j++)
             dist[i][j]=MAXDIST;
}
int SourceForm( int x, int y, int n)
{
     if (x<0 || x>=n || y<0 || y>=n)
         return MAXDIST;
     else
         return dist[x][y];
}
 
int computePath( int n)
{
     if (map[0][0]==1 || map[n-1][n-1]==1)
         return -1;
     InitDist(n);
     dist[0][0]=0;
     
 
     for ( int i=0; i<n; i++)
         for ( int j=0; j<n; j++)
             if (0 == map[i][j])
             {
                 int temp = SourceForm(i-1,j,n)+1;
                 if (SourceForm(i,j-1,n)+1<temp)
                     temp = SourceForm(i,j-1,n)+1;
                 if (SourceForm(i+1,j,n)+1<temp)
                     temp = SourceForm(i+1,j,n)+1;
                 if (SourceForm(i,j+1,n)+1<temp)
                     temp = SourceForm(i,j+1,n)+1;
                 if (temp<dist[i][j])
                     dist[i][j]=temp;
             }
 
     for ( int i=0; i<n; i++)
         for ( int j=n-1; j>=0; j--)
             if (0 == map[i][j])
             {
                 int temp = SourceForm(i-1,j,n)+1;
                 if (SourceForm(i,j-1,n)+1<temp)
                     temp = SourceForm(i,j-1,n)+1;
                 if (SourceForm(i+1,j,n)+1<temp)
                     temp = SourceForm(i+1,j,n)+1;
                 if (SourceForm(i,j+1,n)+1<temp)
                     temp = SourceForm(i,j+1,n)+1;
                 if (temp<dist[i][j])
                     dist[i][j]=temp;
             }
 
     for ( int i=n-1; i>=0; i--)
         for ( int j=0; j<n; j++)
             if (0 == map[i][j])
             {
                 int temp = SourceForm(i-1,j,n)+1;
                 if (SourceForm(i,j-1,n)+1<temp)
                     temp = SourceForm(i,j-1,n)+1;
                 if (SourceForm(i+1,j,n)+1<temp)
                     temp = SourceForm(i+1,j,n)+1;
                 if (SourceForm(i,j+1,n)+1<temp)
                     temp = SourceForm(i,j+1,n)+1;
                 if (temp<dist[i][j])
                     dist[i][j]=temp;
             }
 
     for ( int i=n-1; i>=0; i--)
         for ( int j=n-1; j>=0; j--)
             if (0 == map[i][j])
             {
                 int temp = SourceForm(i-1,j,n)+1;
                 if (SourceForm(i,j-1,n)+1<temp)
                     temp = SourceForm(i,j-1,n)+1;
                 if (SourceForm(i+1,j,n)+1<temp)
                     temp = SourceForm(i+1,j,n)+1;
                 if (SourceForm(i,j+1,n)+1<temp)
                     temp = SourceForm(i,j+1,n)+1;
                 if (temp<dist[i][j])
                     dist[i][j]=temp;
             }
 
     for ( int i=0; i<n; i++)
         for ( int j=0; j<n; j++)
             if (0 == map[i][j])
             {
                 int temp = SourceForm(i-1,j,n)+1;
                 if (SourceForm(i,j-1,n)+1<temp)
                     temp = SourceForm(i,j-1,n)+1;
                 if (SourceForm(i+1,j,n)+1<temp)
                     temp = SourceForm(i+1,j,n)+1;
                 if (SourceForm(i,j+1,n)+1<temp)
                     temp = SourceForm(i,j+1,n)+1;
                 if (temp<dist[i][j])
                     dist[i][j]=temp;
             }
 
     if (dist[n-1][n-1]>=MAXDIST)
         return -1;
     else
         return dist[n-1][n-1];
}
 
 
int main()
{
     //freopen("in.txt","r",stdin);
     int n;
     while (cin>>n)
     {
         for ( int i=0; i<n; i++)
             for ( int j=0; j<n; j++)
                 scanf ( "%d" ,&map[i][j]);
                 //cin>>map[i][j];
         cout<<computePath(n)<<endl;
     }
     return 0;
}
/**************************************************************
     Problem: 1335
     User: xjbscut
     Language: C++
     Result: Accepted
     Time:160 ms
     Memory:1600 kb
****************************************************************/









觉得传递思想绝对是OK的,使用了一种觉得严谨且暴力保证收敛的手段(每次都考虑四个紧邻,把相邻元素是否越界 封装在SourceForm(int x,int y,int n)中很优雅
#include <iostream>
#include <iomanip>
#include <cmath>
//#include <map>
#include <assert.h>
#include <cstring>
#include <string>
#include <vector>
#include <set>
#include <algorithm>
using namespace std;
  
const int MAXDIST = 10000;
int map[105][105];
int dist[105][105];
void InitDist( int n)
{
     for ( int i=0; i<=n; i++) //多取一位 从0取才有意义
         for ( int j=0; j<=n; j++)
             dist[i][j]=MAXDIST;
}
int SourceForm( int x, int y, int n)
{
     if (x<0 || x>=n || y<0 || y>=n)
         return MAXDIST;
     else
         return dist[x][y];
}
int computePath( int n)
{
     if (map[0][0]==1 || map[n-1][n-1]==1)
         return -1;
     InitDist(n);
     dist[0][0]=0;
     bool hasUpadated;
    
     do
     {
         hasUpadated = false ;
         for ( int i=0; i<n; i++)
             for ( int j=0; j<n; j++)
                 if (0 == map[i][j])
                 {
//考虑四个相邻元素
                     int temp = SourceForm(i-1,j,n)+1;
                     if (SourceForm(i,j-1,n)+1<temp)
                         temp = SourceForm(i,j-1,n)+1;
                     if (SourceForm(i+1,j,n)+1<temp)
                         temp = SourceForm(i+1,j,n)+1;
                     if (SourceForm(i,j+1,n)+1<temp)
                         temp = SourceForm(i,j+1,n)+1;
                     if (temp<dist[i][j])
                     {
                         dist[i][j]=temp;
                         hasUpadated =true;
                     }
                 }
 
     } while (hasUpadated);
 
 
     if (dist[n-1][n-1]>=MAXDIST)
         return -1;
     else
         return dist[n-1][n-1];
}
int main()
{
     //freopen("in.txt","r",stdin);
     int n;
     while (cin>>n)
     {
         for ( int i=0; i<n; i++)
             for ( int j=0; j<n; j++)
                 scanf ( "%d" ,&map[i][j]);
                 //cin>>map[i][j];
         cout<<computePath(n)<<endl;
     }
     return 0;
}
/**************************************************************
     Problem: 1335
     User: xjbscut
     Language: C++
     Result: Accepted
     Time:190 ms
     Memory:1596 kb
****************************************************************/


其实撇开这种递推的思路,使用DFS是这种问题的最直观的思路,但是搜索算法相对来说比较难写好,特别使得处理好剪枝,否则很容易超时,可以考虑的是搜索的过程中记录到每一个中间点的当前最短消费(而不是只存储目标点的信息,到了目标节点才进行实际的消费比较),这样可以减少大量不必要的搜索,不过即使这样还是超时,其中第五个测试样例超过了1s,前面通过的样例都只有20ms左右)估计是一个比较强的测试用例,想了下加了个剪纸就AC了,不过时间在AC结果里面还是比较长的(480ms)
#include <stdio.h>
#include <iostream>
#include <iomanip>
#include <cmath>
#include <map>
#include <assert.h>
#include <cstring>
#include <string>
#include <vector>
#include <set>
#include <algorithm>
using namespace std;
const int maxDist = 1000000;
const int size = 101;
int mapInfo[size][size];
bool visited[size][size]; //记录节点是否在当前探查链里
//bool direcNeedToSearch[size][size][4];//四个方向是否都已经探索
int ans[size][size];
 
void Init( int n)
{
     for ( int i=0; i<n; i++)
         for ( int j=0; j<n; j++)
         {
             visited[i][j]= false ;
             ans[i][j]=maxDist;
             //for(int k=0; k<4; k++)
                 //direcNeedToSearch[i][j][k]=true;
         }
     /*if(0 == mapInfo[0][0])
         ans[0][0] = 0;*/
}
void DFS( int curPosX, int curPosY, int curLen, int n)
{
     //先定义出口
     if(curLen + 2*n-2-curPosX-curPosY >= ans[n-1][n-1]) //加上才能通过,一开始觉得帮助不大的
        return;//剪枝
     if (visited[curPosX][curPosY]) //在当前搜索链,构成一个环
         return ;
     if (1 == mapInfo[curPosX][curPosY]) //不可达
         return ;
     if (curPosX == n-1 && curPosY==n-1)
     {
         if (curLen<ans[n-1][n-1])
             ans[n-1][n-1] = curLen;
         return ;
     }
 
     if (curLen>=ans[curPosX][curPosY]) //剪枝
         return ;
     else
         ans[curPosX][curPosY] = curLen;
 
     visited[curPosX][curPosY]= true ; //加入探索链
 
     if (curPosX<n-1)
         DFS(curPosX+1,curPosY,curLen+1,n);
     if (curPosY<n-1)
         DFS(curPosX,curPosY+1,curLen+1,n);
     if (curPosX>0)
         DFS(curPosX-1,curPosY,curLen+1,n);
     if (curPosY>0)
         DFS(curPosX,curPosY-1,curLen+1,n);
 
     //回溯
     visited[curPosX][curPosY]= false ;
}
int main()
{
     //freopen("in.txt","r",stdin);
     int n;
     while (cin>>n)
     {
         Init(n);
         for ( int i=0; i<n; i++)
             for ( int j=0; j<n; j++)
                 scanf ( "%d" ,&mapInfo[i][j]);
                 //cin>>mapInfo[i][j];
         DFS(0,0,0,n);
         if (ans[n-1][n-1] == maxDist)
             cout<<-1<<endl;
         else
             cout<<ans[n-1][n-1]<<endl;
     }
     return 0;
}
/**************************************************************
     Problem: 1335
     User: xjbscut
     Language: C++
     Result: Accepted
     Time:480 ms
     Memory:1708 kb
****************************************************************/

另外看到网上有评论使用BFS求解,觉得不是太可行,这里不是顶点的遍历,而是考虑所有到给定顶点的最近距离,使用队列的时候顶点入队以值最小的先入队 而不是存储的顺序队列里得运行一个顶点同时出现多次???(因为出队才是最终结果固定下来)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值