算法思路:DFS。
关键:奇偶剪枝 + 递归的构造。
下面是转自别人博客上的分析过程(链接:http://jidayangfei.blog.163.com/blog/static/134936608201073011445108/):
问题:
(1):
在发现当前节点无法到达时,这点弹出栈,并且把这点的标记重新刷为'.'
(2):
如何在dfs中既要保证到达又要使时间正好呢?? 在函数中通过这种形式实现:
dfs(int si,int sj,int cnt) 就是用cnt来记录当时的时间,并且在
if( si==di && sj==dj && cnt==t )
{
escape = 1;
return;
}
的时候 即当前点到达了终点并且时间恰好等于题目所给限制时间时,跳出
并且escape标记为真
(3):
如何让一个点有顺序地遍历它四周地能到达的点呢??
聪明并且简短的方法是设施一个dir[4][2] 数组 控制方向
并且设置它的值为dir[4][2]={{0,-1},{0,1},{1,0},{-1,0}};
遍历的时候用for(i:0->4)就非常方便了
(4):
千万要注意的是节点越界的情况, dfs(int si,int sj,int cnt)的时候一定要把 si, sj 控制在给你的矩阵内 在后面会提到一个我的列子 就是因为访问了[0, -1]的位置导致了其
他数据被更改
(5):
读入矩阵的时候,可以采用for(i = 1; i <= N; i++)
for(j = 1; j <= M; j++)
scanf("%c", &map[i][j]);
的方法,好处在于可以控制和计算每一个读入的数据,坏处是调试的时候对矩阵的观察不太方便,而且好像还会有错误,在2102"A计划"用这种方法读入数据时好像就会wa,
另一种方法是for(i = 0; i < N; i++) gets(map[i]);
这样读入的数据在调试观察的时候十分方便 gets()读入的默认为字符串,在vc调试的时候是显式的 可以直接观察矩阵 缺点是对矩阵中各个数据的计算和控制无法实现,需要读完后再遍历一遍
(6)
能用bfs还是尽量用bfs 我不会bfs. dfs的递归在调试的时候不是很方便,而且bfs要比dfs快,调试也要方便,因为它没有递归
(7)
关于剪枝,没有剪枝的搜索不太可能,这题老刘上课的时候讲过两个剪枝,一个是奇偶剪枝,一个是路径剪枝
奇偶剪枝:
把矩阵标记成如下形式:
0,1,0,1,0
1,0,1,0,1
0,1,0,1,0
1,0,1,0,1
很明显,如果起点在0 而终点在1 那显然 要经过奇数步才能从起点走到终点,依次类推,奇偶相同的偶数步,奇偶不同的奇数步
在读入数据的时候就可以判断,并且做剪枝,当然做的时候并不要求把整个矩阵0,1刷一遍,读入的时候起点记为(Si,Sj) 终点记为(Di,Dj) 判断(Si+Sj) 和 (Di+Dj) 的奇偶性就可以了
路径剪枝:
矩阵的大小是N*M 墙的数量记为wall 如果能走的路的数量 N*M - wall 小于时间T,就是说走完也不能到总的时间的,这显然是错误的,可以直接跳出了
课件里面给过这题的标程,在dfs的过程中有个没提到的剪枝,就是记录当前点到终点的最短路,如果小于剩余的时间的话,就跳出
这个剪枝我觉得更科学,它毕竟是动态的么,标程里面是这么写的:
temp = (t-cnt) - abs(si-di) - abs(sj-dj);
if( temp<0 || temp&1 ) return;
其中求当前点到终点的最短路是这样 abs(si-di) - abs(sj-dj) 这个就比较粗糙了 明显没有考虑到碰到墙要拐弯的情况
那求最短路有没有什么好办法呢?
我曾经想到过用 Dijkstraq求最短路的 ,明显大才小用,在论坛里看到一个方法觉得可以用在这里
给定下例:
S.X.
..X.
..XD
.
每个点到终点的最短路是不是这样呢:
S6X2
65X1
54XD
4321
这怎么求呢??从终点开始遍历整个数组,终点是0,它周围的点都+1,墙就不计数,依次类推,就能求得这个矩阵的一个最短时间矩阵,在dfs的时候比较当前点到终点的最短路,如果大于剩余时间的话就跳出
这个方法的预处理还是非常快的,我没有用过,但是感觉会非常有用处.
46msAC,这个代码是看过上面的blog的代码之后自己重敲的,如下:
//模板开始
#include <string>
#include <vector>
#include <algorithm>
#include <iostream>
#include <sstream>
#include <fstream>
#include <map>
#include <set>
#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <ctime>
#include<iomanip>
#include<string.h>
#define SZ(x) (int(x.size()))
using namespace std;
int toInt(string s){
istringstream sin(s);
int t;
sin>>t;
return t;
}
template<class T> string toString(T x){
ostringstream sout;
sout<<x;
return sout.str();
}
typedef long long int64;
int64 toInt64(string s){
istringstream sin(s);
int64 t;
sin>>t;
return t;
}
template<class T> T gcd(T a, T b){
if(a<0)
return gcd(-a, b);
if(b<0)
return gcd(a, -b);
return (b == 0)? a : gcd(b, a % b);
}
//模板结束(通用部分)
#define ifs cin
#define MAX_LEN 10
char maze[MAX_LEN][MAX_LEN];
int n, m, t;
int di, dj, si, sj;
int escape;
int dir[4][2] = {{0,-1},{0,1},{1,0},{-1,0}}; //分别对应 左、右、下、上
void dfs(int di, int dj, int cnt) //di,dj代表门的坐标,cnt代表距离门的步数
{
if(di > n || di < 1 || dj > m || dj < 1)
{
return;
}
if(di == si && dj == sj && cnt == t)
{
escape = 1;
return;
}
int temp = (t - cnt) - (abs(si - di) + abs(sj - dj)); //这个式子的构造很重要
if(temp < 0 || temp % 2) //路劲剪枝 + 奇偶剪枝
{
return;
}
for(int i = 0; i < 4; i++)
{
if(maze[di + dir[i][0]][dj + dir[i][1]] != 'X')
{
maze[di + dir[i][0]][dj + dir[i][1]] = 'X'; //标志为已经访问过,下次不能再访问
dfs(di + dir[i][0], dj + dir[i][1], cnt + 1);
if(escape == 1)
{
return;
}
maze[di + dir[i][0]][dj + dir[i][1]] = '.'; //escape == 0才执行,让方格重新恢复访问权限
}
}
}
//【练习06】BFS 1001 Tempter of the Bone
int main()
{
//ifstream ifs("shuju.txt", ios::in);
while(ifs>>n>>m>>t && !(n == 0 && m == 0 && t == 0))
{
int wall = 0;
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= m; j++)
{
ifs>>maze[i][j];
if(maze[i][j] == 'D') //di, dj代表门的坐标
{
di = i;
dj = j;
}
else if(maze[i][j] == 'S') //si, sj代表小狗的坐标
{
si = i;
sj = j;
}
else if(maze[i][j] == 'X')
{
wall++;
}
}
}
if(n * m - wall < t + 1)
{
cout<<"NO"<<endl;
continue;
}
escape = 0;
maze[di][dj] = 'X';
dfs(di, dj, 0); //从门开始搜索
if(escape == 1)
{
cout<<"YES"<<endl;
}
else
{
cout<<"NO"<<endl;
}
}
return 0;
}