题目描述
解题思路
剪枝
剪枝策略就是在搜索过程中利用过滤条件来剪去完全不用考虑(已经判断这条路走下去得不到最优解)的搜索路径,从而避免了一些不必要的搜索,大大优化了算法求解速度,还保证了结果的正确性。
简单的说就是把不可行的一些情况剪掉,例如走迷宫时运用回溯法,遇到死胡同时回溯,造成程序运行时间长。剪枝的概念,其实就跟走迷宫避开死胡同差不多。若我们把搜索的过程看成是对一棵树的遍历,那么剪枝顾名思义,就是将树中的一些“死胡同”,不能到达我们需要的解的枝条“剪”掉,以减少搜索的时间。
奇偶剪枝
把矩阵看成如下形式:(0和1代表奇偶性)
0 1 0 1 0 1
1 0 1 0 1 0
0 1 0 1 0 1
1 0 1 0 1 0
0 1 0 1 0 1
从为 0 的格子走一步,必然走向为 1 的格子 。
从为 1 的格子走一步,必然走向为 0 的格子 。
即: 从 0 走向 1 必然是奇数步,从 0 走向 0 必然是偶数步。
所以 当遇到从 0 走向 0 (从 1 走向 1 ) 但是要求时间是奇数的
或者 从 1 走向 0 (从 0 走向 1 ) 但是要求时间是偶数的
都可以直接判断不可达!
比如有一地图:
S...
....
....
....
...D
要求从S点到达D点,此时,从S到D的最短距离为
s = abs ( dx - sx ) + abs ( dy - sy )
如果地图中出现了不能经过的障碍物:
S..X
XX.X
...X
.XXX
...D
此时的最短距离 s' = s + 4
绕开障碍就会偏移原路线,但不管偏移几个点,最终都要回到原路径上
偏移的距离都是最短距离 s 加上一个偶数距离(因为一出一进)
就如同上面说的矩阵
要求你从0走到0,无论你怎么绕,永远都是最短距离 (偶数步) 加上偶数步
要求你从1走到0,永远只能是最短距离 (奇数步) 加上偶数步
结论:路径的偏移量永远为偶数。
因为最短路径步数 + 偏移量(偶数)= 某一可行解歩数
又 偶数 + 偶数 = 偶数,奇数 + 偶数 = 奇数
故 某一可行解步数的奇偶性由最短路径步数决定
即 最短路歩数和某一可行解歩数的奇偶性相同
奇偶剪枝最常用的就是在t时刻到达终点这种问题。
简而言之,就是我们当前点距终点的最短步数+我们现在走过的步数一定要与t的奇偶性相同,否则不可能在t时刻到达。
dis=t-step-abs(ex-x)-abs(ey-y);
代码
#include<cstring>
#include<cmath>
#include<iostream>
using namespace std;
int n,m,t,sx,sy,ex,ey,flag,vis[10][10];
char map[10][10]; //存图
int dx[]={0,-1,0,1},dy[]={1,0,-1,0}; //上下左右四个方向
void dfs(int x,int y,int step){
if(flag)return ; //剪枝,如果已经找到了就没必要再找了
int dis =t-step-abs(ex-x)-abs(ey-y);
if ( dis < 0 || dis &1 ) return; // 剩余步数小于最短距离或者满足奇偶剪枝条件
vis[x][y]=1;
if(step==t){
if(map[x][y]=='D'){flag=1;return;} //如果是终点就标记flag为1,然后退出
else return; //如果不是终点就直接退出
}
for(int i=0;i<4;i++){
int u=x+dx[i],v=y+dy[i];
if(u<1||u>n||v<1||v>m||map[u][v]=='X'||vis[u][v]) continue; //如果越界、已访问或者是障碍就跳过
vis[u][v]=1; //否则就标记为已访问
dfs(u,v,step+1); //进一步搜索
vis[u][v]=0; //回溯
}
}
int main()
{
while(cin>>n>>m>>t&&n&&m&&t){
memset(map,0,sizeof map); //每组测试都需要初始化,以免出现意外
memset(vis,0,sizeof vis);
flag=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>map[i][j];
if(map[i][j]=='S'){sx=i;sy=j;}
if(map[i][j]=='D'){ex=i;ey=j;}
}
}
dfs(sx,sy,0);
if(flag) cout<<"YES"<<endl;
else cout<<"NO"<<endl;
}
return 0;
}