DFS要点:
1.根据是否需要“找到可达路径立即结束”,可以给DFS函数设置返回值为int或void,这样的话可根据返回值判断return(当然也可以设置一个全局变量flag,效果相同,但感觉返回值更好);
2.DFS函数传给下一次递归的变量视情况而定,一般情况下是路径的长度,每次都+1;
3.DFS函数一般可以分为3个部分:不满足情况直接return,满足条件记录并return,分别朝四个方向探索,并注意递归和回溯;
4.如果规模太大,比如100×100,DFS时间会扛不住,考虑记录,即DP思维(如下面的第二题)。
小鼠迷宫问题
Time Limit: 1500ms Memory limit: 65536K 有疑问?点这里^_^
题目描述
小鼠a与小鼠b身处一个m×n的迷宫中,如图所示。每一个方格表示迷宫中的一个房间。这m×n个房间中有一些房间是封闭的,不允许任何人进入。在迷宫中任何位置均可沿上,下,左,右4个方向进入未封闭的房间。小鼠a位于迷宫的(p,q)方格中,它必须找出一条通向小鼠b所在的(r,s)方格的路。请帮助小鼠a找出所有通向小鼠b的最短道路。
请编程对于给定的小鼠的迷宫,计算小鼠a通向小鼠b的所有最短道路。
![](http://acm.sdut.edu.cn/image/1157.gif)
请编程对于给定的小鼠的迷宫,计算小鼠a通向小鼠b的所有最短道路。
输入
本题有多组输入数据,你必须处理到EOF为止。
每组数据的第一行有3个正整数n,m,k,分别表示迷宫的行数,列数和封闭的房间数。
接下来的k行中,每行2个正整数,表示被封闭的房间所在的行号和列号。
最后的2行,每行也有2个正整数,分别表示小鼠a所处的方格(p,q)和小鼠b所处的方格(r,s)。
每组数据的第一行有3个正整数n,m,k,分别表示迷宫的行数,列数和封闭的房间数。
接下来的k行中,每行2个正整数,表示被封闭的房间所在的行号和列号。
最后的2行,每行也有2个正整数,分别表示小鼠a所处的方格(p,q)和小鼠b所处的方格(r,s)。
输出
对于每组数据,将计算出的小鼠a通向小鼠b的最短路长度和有多少条不同的最短路输出。
每组数据输出两行,第一行是最短路长度;第2行是不同的最短路数。
每组输出之间没有空行。
如果小鼠a无法通向小鼠b则输出“No Solution!”。
每组数据输出两行,第一行是最短路长度;第2行是不同的最短路数。
每组输出之间没有空行。
如果小鼠a无法通向小鼠b则输出“No Solution!”。
示例输入
8 8 3 3 3 4 5 6 6 2 1 7 7
示例输出
11 96
来源
NOI’2005福建省选手选拔赛
#include<iostream>
#include<algorithm>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
using namespace std;
struct node
{
int x;
int y;
}a,b;
int move_x[] = {0,1,0,-1};
int move_y[] = {1,0,-1,0};
int v[101][101]; //标记是否到达过
int map[101][101];
int n,m,k;
int flag;
int ans; //已知最短路的长度
int psum; //当前已知的最短路找到的次数
void DFS(int xx,int yy,int cnt) //cnt : 当前距离
{
if(cnt>ans) //结束条件。已然大于曾找过的最短路了,再找下去也没意义了
{
return ;
}
if(xx == b.x && yy == b.y)
{
if(cnt<ans) //如果此路更短,则之前的较长的答案可以抛弃了
{
flag = 1; //标记,找到了可达的路
ans = cnt;
psum = 1;
}
else if(cnt == ans) //如果和之前的路一样长,记下来
{
psum++;
}
return ;
}
for(int i=0;i<4;i++) //朝四个方向探索
{
int tmpX = xx + move_x[i];
int tmpY = yy + move_y[i];
if(tmpX >= 0 && tmpX < n && tmpY >= 0 && tmpY < m //判断越界
&& map[tmpX][tmpY] == 0 && v[tmpX][tmpY] == 0) //判断可达到且没到达过
{
v[tmpX][tmpY] = 1;
DFS(tmpX, tmpY, cnt + 1); //DFS,路径长度+1
v[tmpX][tmpY] = 0; //回溯
}
}
}
int main()
{
while(scanf("%d%d%d",&n,&m,&k)!=EOF)
{
for(int i=0;i<=n;i++)
{
for(int j=0;j<=m;j++)
{
map[i][j] = 0;
v[i][j] = 0;
}
}
int x,y;
for(int i=0;i<k;i++)
{
scanf("%d%d",&x,&y);
map[x][y] = 1;
}
ans = 99999999;
flag = 0;
psum = 0;
scanf("%d%d",&a.x,&a.y);
scanf("%d%d",&b.x,&b.y);
DFS(a.x,a.y,0);
(flag == 0) ? printf("No Solution!\n") : printf("%d\n%d\n", ans, psum);
}
return 0;
逃离迷宫
Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 12273 Accepted Submission(s): 2964
Problem Description
给定一个m × n (m行, n列)的迷宫,迷宫中有两个位置,gloria想从迷宫的一个位置走到另外一个位置,当然迷宫中有些地方是空地,gloria可以穿越,有些地方是障碍,她必须绕行,从迷宫的一个位置,只能走到与它相邻的4个位置中,当然在行走过程中,gloria不能走到迷宫外面去。令人头痛的是,gloria是个没什么方向感的人,因此,她在行走过程中,不能转太多弯了,否则她会晕倒的。我们假定给定的两个位置都是空地,初始时,gloria所面向的方向未定,她可以选择4个方向的任何一个出发,而不算成一次转弯。gloria能从一个位置走到另外一个位置吗?
Input
第1行为一个整数t (1 ≤ t ≤ 100),表示测试数据的个数,接下来为t组测试数据,每组测试数据中,
第1行为两个整数m, n (1 ≤ m, n ≤ 100),分别表示迷宫的行数和列数,接下来m行,每行包括n个字符,其中字符'.'表示该位置为空地,字符'*'表示该位置为障碍,输入数据中只有这两种字符,每组测试数据的最后一行为5个整数k, x 1, y 1, x 2, y 2 (1 ≤ k ≤ 10, 1 ≤ x 1, x 2 ≤ n, 1 ≤ y 1, y 2 ≤ m),其中k表示gloria最多能转的弯数,(x 1, y 1), (x 2, y 2)表示两个位置,其中x 1,x 2对应列,y 1, y 2对应行。
第1行为两个整数m, n (1 ≤ m, n ≤ 100),分别表示迷宫的行数和列数,接下来m行,每行包括n个字符,其中字符'.'表示该位置为空地,字符'*'表示该位置为障碍,输入数据中只有这两种字符,每组测试数据的最后一行为5个整数k, x 1, y 1, x 2, y 2 (1 ≤ k ≤ 10, 1 ≤ x 1, x 2 ≤ n, 1 ≤ y 1, y 2 ≤ m),其中k表示gloria最多能转的弯数,(x 1, y 1), (x 2, y 2)表示两个位置,其中x 1,x 2对应列,y 1, y 2对应行。
Output
每组测试数据对应为一行,若gloria能从一个位置走到另外一个位置,输出“yes”,否则输出“no”。
Sample Input
2 5 5 ...** *.**. ..... ..... *.... 1 1 1 1 3 5 5 ...** *.**. ..... ..... *.... 2 1 1 1 3
Sample Output
no yes
#include<iostream>
#include<cstdio>
#include<cstring>
#define N 101
#define INITIAL_DIR -3
#define FIND 1
#define NOT 0
using namespace std;
char map[N][N];
int turnTimes[N][N]; //记录到达每一个点时最小的转向次数,可以根据这个判断当前点是否已经走过,也可以进行剪枝
int times, m, n, k, xx1, yy1, xx2, yy2;
int a[4][2] = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}};
void init_turnTimes()
{
for(int i = 0; i < N; i++)
{
for(int j = 0; j < N; j++)
{
turnTimes[i][j] = 0x3fffffff;
}
}
}
int DFS(int x, int y, int dir) //带返回值,可以方便的根据返回值得到“是否已经找到答案”这一信息
{
if(turnTimes[x][y] > k || (x != xx2 && y != yy2 && turnTimes[x][y] == k)) //结束条件,其实第二个可以不要,效果并不明显
{
return NOT;
}
if(x == xx2 && y == yy2) //结束条件,发现了终点
{
return FIND;
}
for(int i = 0; i < 4; i++) //分别向四个方向探索
{
int tmpX = x + a[i][0];
int tmpY = y + a[i][1];
if(tmpX >= 0 && tmpX < m && tmpY >= 0 && tmpY < n //越界条件
&& map[tmpX][tmpY] != '*' && turnTimes[tmpX][tmpY] >= turnTimes[x][y]) //要探索的点可达且没去过
{
//剪枝条件。相当于DP的记录,因为100×100直接DFS太丧心病狂
//其实可以在if判断语句里用turnTimes[tmpX][tmpY] > turnTimes[x][y]直接剪掉,但是为了突出剪枝条件,刻意保留,在此处展示
//如果需要转弯+1,且之前在此点转向次数和过来的点已然相同,就不用再探索了,因为如果之前都不符合条件,+1之后更不可能符合条件了
if(dir != INITIAL_DIR && dir !=i && turnTimes[tmpX][tmpY] == turnTimes[x][y])
continue;
if(dir != INITIAL_DIR && dir != i)
{
turnTimes[tmpX][tmpY] = turnTimes[x][y] + 1;
}
else
{
turnTimes[tmpX][tmpY] = turnTimes[x][y];
}
if(DFS(tmpX, tmpY, i) == FIND) //回溯,且已经找到立即return,不必再继续向后探索。
{
return FIND;
}
}
}
return NOT;
}
int main()
{
cin >> times;
while(times--)
{
memset(map, 0, sizeof(map));
init_turnTimes();
cin >> m >> n;
for(int i = 0; i < m; i++)
{
cin >> map[i];
}
cin >> k >> yy1 >> xx1 >> yy2 >> xx2; //建议以后设成sx, sy; ex, ey; 因为y1已经被math.h占用了,丧心病狂
xx1--, yy1--, xx2--, yy2--;
turnTimes[xx1][yy1] = 0; //将起始点的转向次数初始化为0
(DFS(xx1, yy1, INITIAL_DIR) == FIND) ? cout << "yes\n" : cout << "no\n";
}
return 0;
}
在这里顺便奉上这一题的BFS的源码。值得注意的是,和普通的BFS以距离为关键点比起来,这一题的BFS有点儿变化,已经在代码中标出:
#include<iostream>
#include<cstring>
#include<cstdio>
#include<string>
#include<queue>
using namespace std;
int enx,eny,m,n;
char maze[105][105];
int totalStep; //totalStep: total directions
int visi[105][105]; //visi : visited
int turnTimes[105][105]; //turnTimes: turn around times of every vertex
int dir[4][2]={{-1,0},{1,0},{0,1},{0,-1}};
struct node
{
int x;
int y;
};
void BFS(node p)
{
queue<node> mq;
mq.push(p); // push the start point in the queue
node curPoint, nextPoint;
while(!mq.empty())
{
curPoint = mq.front();
mq.pop();
if(turnTimes[curPoint.x][curPoint.y] >= totalStep) break;
int tmpX, tmpY;
for(int i = 0; i < 4; i++)
{
tmpX = curPoint.x + dir[i][0], tmpY = curPoint.y + dir[i][1];
while(tmpX >= 0 && tmpX < m && tmpY >= 0&& tmpY< n //越界判断
&& maze[tmpX][tmpY] != '*') //可达
{
if(!visi[tmpX][tmpY])
{
visi[tmpX][tmpY] = 1; // 将探索到的可达的点nextPoint标记
nextPoint.x = tmpX, nextPoint.y = tmpY;
turnTimes[tmpX][tmpY] = turnTimes[curPoint.x][curPoint.y] + 1; // +1
mq.push(nextPoint); // 将nextPoint入队
}
tmpX += dir[i][0], tmpY += dir[i][1]; //朝一个方向走到底!
//由于本题的关键是转向的次数,所以和普通的BFS比起来(普通BFS关键是“距离”),关键不再是距离,于是只要方向没变,即使距离不一样,那么这些格也是等价的,所以“朝一个方向走到底”是这个题的“转向次数是关键”的解决办法。
}
}
}
}
int main()
{
int times, stx, sty;
cin >> times;
while(times--)
{
cin>>m>>n;
for(int i = 0; i < m; i++)
cin >> maze[i];
cin >> totalStep;
cin >> stx >> sty >> enx >> eny;
stx--,sty--,enx--,eny--;
swap(stx,sty); swap(enx,eny);
node p;
p.x = stx,p.y = sty;
memset(visi,0,sizeof(visi));
for(int i = 0; i < m; i++)
for(int j = 0; j < n; j++)
turnTimes[i][j] = 1e8;
visi[p.x][p.y] = 1;
turnTimes[p.x][p.y] = -1;
BFS(p);
(turnTimes[enx][eny] <= totalStep) ? cout << "yes" << endl : cout << "no" << endl;
}
return 0;
}