佐助被大蛇丸诱骗走了,鸣人在多少时间内能追上他呢?
已知一张地图(以二维矩阵的形式表示)以及佐助和鸣人的位置。地图上的每个位置都可以走到,只不过有些位置上有大蛇丸的手下,需要先打败大蛇丸的手下才能到这些位置。鸣人有一定数量的查克拉,每一个单位的查克拉可以打败一个大蛇丸的手下。假设鸣人可以往上下左右四个方向移动,每移动一个距离需要花费1个单位时间,打败大蛇丸的手下不需要时间。如果鸣人查克拉消耗完了,则只可以走到没有大蛇丸手下的位置,不可以再移动到有大蛇丸手下的位置。佐助在此期间不移动,大蛇丸的手下也不移动。请问,鸣人要追上佐助最少需要花费多少时间?
输入的第一行包含三个整数:M,N,T。代表M行N列的地图和鸣人初始的查克拉数量T。0 < M,N < 200,0 ≤ T < 10
后面是M行N列的地图,其中@代表鸣人,+代表佐助。*代表通路,#代表大蛇丸的手下。
输出包含一个整数R,代表鸣人追上佐助最少需要花费的时间。如果鸣人无法追上佐助,则输出-1。
样例输入
样例输入1
4 4 1
#@##
**##
###+
****
样例输入2
4 4 2
#@##
**##
###+
****
样例输出
样例输出1
6
样例输出2
4
dfs+剪枝
dfs:
首先dfs鸣人的点,再接着dfs鸣人四周的各点;
终止条件——查克拉<0
最优性剪枝:
*剪枝1:*创建三维数组sep,初始值设置为最大值,将当前x,y,查克拉所费的时间数(temptime)保存至sep[x][y][查克拉],若再次来到该点x,y且拥有相同的查克拉,而时间数更大的话,那此次dfs的总时间数必将大于前者,即不可能为最少时间——遗弃之。
*剪枝2:*当前所用时间大于最快的总时间——遗弃之。
#include
#include
using namespace std;
int Time; //找到佐助的最少时间
int temptime; //过程中的时间数,每次dfs前归零
int M,N,T; //M为行数,N为列数,T为查克拉数
char map[210][210]; //保存地图
int narotoX; //鸣人的初始行
int narotoY; //鸣人的初始列
int visited[210][210]; //初始为0,当此遍历时走过则标记为1,防止重复走同一个点.
int lx[4]={1,-1,0,0}; //下一个行数的遍历数组
int ly[4]={0,0,1,-1}; //下一个列数的遍历数组
int x1,y1;
int sep[210][210][12]; //三维数组————为最优性剪枝用
void Dfs(int x, int y,int chakora){
if(x<0||x>=M||y<0||y>=N||visited[x][y]) return; //走到地图外了——直接失败返回
if (chakora<0) return; //查克拉小于0,直接失败返回
if(temptime>=Time) return; //剪枝2,失败返回
if(temptime>=sep[x][y][chakora]) return; //剪枝1,失败返回
sep[x][y][chakora]=temptime; //这时temptime必小于sep[x][y][chakora],保存之
if(map[x][y]=='#') chakora--; //当前xy有敌人,查克拉减少
else if (map[x][y]=='+'){ //当前xy有佐助
if(temptime<Time) Time=temptime; //判断此时temptime是小于最少时间,是则保存之
return;
}
visited[x][y]=1; //标记为已访问
temptime++; //时间+上
for(int i=0;i<4;i++){ //dfs上下左右四个点
x1=x+lx[i];
y1=y+ly[i];
Dfs(x1,y1,chakora);
}
visited[x][y]=0; //都遍历完成——返回上层并将此点标记为未访问
temptime--; //访问这个点用的时间也剪掉
}
int main(){
while(cin>>M>>N>>T){
for (int i=0;i<M;i++)
for (int j=0;j<N;j++){
cin>>map[i][j];
if (map[i][j]=='@'){
narotoX=i;
narotoY=j;}} //保存鸣人的位置
memset(sep,0x3f,sizeof sep); //初始化sep为极大值
//关于为什么要赋值为0x3f↓
//http://www.manongjc.com/detail/10-jreyaemnqbgivzi.html
Time=1<<30;
temptime=0; //初始化temptime
Dfs(narotoX,narotoY,T);
if (Time==1<<30) cout<<"-1"<<endl;
else cout<<Time<<endl;
}
return 0;
}
BFS算法
找到的第一个解一定是最短解,无需像dfs一样考虑剪枝
#include
#include
#include
using namespace std;
//关键思路:状态比位置多一维,即处于该位置时的查克拉数目
//无需剪枝——找最优解比dfs方便
int M,N,T;
char map[220][220];
int Nx,Ny;
int dp[4][2]={{1,0},{-1,0},{0,1},{0,-1}};
int x1,y1;
int flag;
struct NRT{
int x;
int y;
int steps; //所需步数
int C;//查克拉
NRT(int xx,int yy,int s,int cc):x(xx),y(yy),C(cc),steps(s){
} //初始化结构函数,方便转移
};
//创建队列,存放头节点
int visited[220][220][15];
//访问过的xy 是三位数组——还包括查克拉数 !!!有三个状态)——三维数组!很关键
int main(){
while(cin>>M>>N>>T){
queue <NRT>F; //每次输入都要清空队列——很关键
for (int i =0;i<M;i++)
for (int j=0;j<N;j++){
cin>>map[i][j];
if (map[i][j]=='@'){
Nx=i; //鸣人所在的行
Ny=j; //鸣人所在的列
}
}
//找最少时间,用BFS更好
//设定起始位置
memset(visited,0,sizeof visited);
F.push(NRT(Nx,Ny,0,T)); //鸣人入队
visited[Nx][Ny][T]=1;
while(!F.empty()){
flag=0;
NRT s=F.front();
for (int t=0;t<4;t++){
x1=s.x+dp[t][0];
y1=s.y+dp[t][1];
if(x1<0||x1>=M||y1<0||y1>=N) continue;//超出地图边界
if(visited[x1][y1][s.C]) continue; //已入过队列
if(map[x1][y1]=='+'){ //找到
cout<<s.steps+1<<endl; //输出步数+1
flag=1; //退出标记
break; //退出
}
if(map[x1][y1]=='#'&&s.C>0){
F.push(NRT(x1,y1,s.steps+1,s.C-1)); //查克拉-1
visited[x1][y1][s.C-1]=1;
}
if(map[x1][y1]=='*'){ //查克拉不减
F.push(NRT(x1,y1,s.steps+1,s.C));
visited[x1][y1][s.C]=1;
}
}
F.pop();
if(flag==1){
break;
}
}
if (flag==0) { //没找到
cout<<"-1"<<endl;
}
}
return 0;
}