题目传送门:
https://www.luogu.com.cn/problem/P2802
题解思路:
雷区:回溯操作
在深度优先搜索的题目中,回溯是非常常见的一种操作,因此我最开始把回溯加上了,然后顺利的通过9个样例,最后一个死活A不了;最后分析发现算法是有问题的,因为如果采用回溯操作的话,那么小H在移动到“鼠标”位置后然后返回原位置时会产生矛盾,按照回溯方法 此时的血量和步数应该与移动前一致,但是如果小H本来就是想吃“鼠标”加血的,而此时回溯则让他没法达到加血目的从而最终可能因为血量不够回不了家。并且加了血就一定加步数,不加血则不加步数,这是捆绑在一起的。回溯法没法完成这个情况,无法分辨小H的目的是加血还是回退,因此最后取消掉回溯法。我们在dfs中加上回溯的主要目的就是防止来回在几个位置移动从而出现死循环的情况,但是我们可以增加一些操作来防止无限制地搜索,如以下的代码:if(xue<=0) return; //直接退出 这当小H在几个位置来回移动时血量不断消耗,最终也会死去而退出搜索。 但是如果在有鼠标的地方来回搜索呢,这时上面的限制不起作用了,而我们又增加以下代码: if(step>=n*m) return;//剪枝操作 if(step>path) return;//剪枝操作,这两个操作对步数进行了限制,同样起到剪枝优化的作用,并且综合这几个代码不会出现死循环的情况。
AC代码:
#include<iostream> #include<algorithm> #include<string> using namespace std; int n,m; int a[10][10]; //储存地图 int color[10][10]={0}; //标记用,防止出现重复走动而死循环的情况 //注意 虽然有标记但是这里是搜索路径问题 所以在每层的dfs结束时都需要进行回溯 int xue=6; //初始的血量为6 int path=120; //最短路长 // int step=0; int dx[4]={-1,0,1,0}; int dy[4]={0,-1,0,1}; void dfs(int i,int j,int xue,int step){ //先判断是否死亡 if(xue<=0) return; //直接退出 if(step>=n*m) return;//剪枝操作 if(step>path) return;//剪枝操作 if(a[i][j]==3){ if(path>step){//剪枝操作 path=step; //更新最小路径 } return; } if(a[i][j]==4) xue=6; //血量加满 for(int k=0;k<4;k++){ int tx=i+dx[k]; int ty=j+dy[k]; if(a[tx][ty]!=0){ dfs(tx,ty,xue-1,step+1); } } } int main(){ cin>>n>>m; for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ cin>>a[i][j]; } } for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ if(a[i][j]==2){ //出发地 dfs(i,j,6,0); break; } } } if(path==120){ //表示没回家 cout<<"-1"; } else{ cout<<path; } return 0; }
使用回溯时的代码(与上面进行对比,只通过了8个样例):
#include<iostream> #include<algorithm> #include<string> using namespace std; int n,m; int a[10][10]; //储存地图 int color[10][10]={0}; //标记用,防止出现重复走动而死循环的情况 //注意 虽然有标记但是这里是搜索路径问题 所以在每层的dfs结束时都需要进行回溯 int xue=6; //初始的血量为6 int path=120; //最短路长 int step=0; int dx[4]={-1,0,1,0}; int dy[4]={0,-1,0,1}; void dfs(int i,int j){ if(color[i][j]==1) return; //表示之前走过 //先判断是否死亡 if(xue<=0) return; //直接退出 if(a[i][j]==3){ //剪枝操作 if(path>step){ path=step; //更新最小路径 } return; } color[i][j]=1; //标记 if(a[i][j]==4) xue=6; //血量加满 for(int k=0;k<4;k++){ int tx=i+dx[k]; int ty=j+dy[k]; if(a[tx][ty]!=0){ step++; // 只有移动时才有步数加1 xue--; //顺便血量先减去1 dfs(tx,ty); step--; xue++; //xue和step是捆绑的 } } color[i][j]=0; //回溯的时候标记取消 血量回加 } int main(){ cin>>n>>m; for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ cin>>a[i][j]; } } for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ if(a[i][j]==2){ //出发地 dfs(i,j); } } } if(path==120){ //表示没回家 cout<<"-1"; } else{ cout<<path; } return 0; }