题目
1804:小游戏
总时间限制: 1000ms 内存限制: 65536kB
描述
一天早上,你起床的时候想:“我编程序这么牛,为什么不能靠这个赚点小钱呢?”因此你决定编写一个小游戏。
游戏在一个分割成w * h个正方格子的矩形板上进行。如图所示,每个正方格子上可以有一张游戏卡片,当然也可以没有。
当下面的情况满足时,我们认为两个游戏卡片之间有一条路径相连:路径只包含水平或者竖直的直线段。路径不能穿过别的游戏卡片。但是允许路径临时的离开矩形板。下面是一个例子:
这里在 (1, 3)和 (4, 4)处的游戏卡片是可以相连的。而在 (2, 3) 和 (3, 4) 处的游戏卡是不相连的,因为连接他们的每条路径都必须要穿过别的游戏卡片。
你现在要在小游戏里面判断是否存在一条满足题意的路径能连接给定的两个游戏卡片。
输入
输入包括多组数据。一个矩形板对应一组数据。每组数据包括的第一行包括两个整数w和h (1 <= w, h <= 75),分别表示矩形板的宽度和长度。下面的h行,每行包括w个字符,表示矩形板上的游戏卡片分布情况。使用‘X’表示这个地方有一个游戏卡片;使用空格表示这个地方没有游戏卡片。
之后的若干行上每行上包括4个整数x1, y1, x2, y2 (1 <= x1, x2 <= w, 1 <= y1, y2 <= h)。给出两个卡片在矩形板上的位置(注意:矩形板左上角的坐标是(1, 1))。输入保证这两个游戏卡片所处的位置是不相同的。如果一行上有4个0,表示这组测试数据的结束。
如果一行上给出w = h = 0,那么表示所有的输入结束了。
输出
对每一个矩形板,输出一行“Board #n:”,这里n是输入数据的编号。然后对每一组需要测试的游戏卡片输出一行。这一行的开头是“Pair m: ”,这里m是测试卡片的编号(对每个矩形板,编号都从1开始)。接下来,如果可以相连,找到连接这两个卡片的所有路径中包括线段数最少的路径,输出“k segments.”,这里k是找到的最优路径中包括的线段的数目;如果不能相连,输出“impossible.”。
每组数据之后输出一个空行。
样例输入
5 4
XXXXX
X X
XXX X
XXX
2 3 5 3
1 3 4 4
2 3 3 4
0 0 0 0
0 0
样例输出
Board #1:
Pair 1: 4 segments.
Pair 2: 3 segments.
Pair 3: impossible.
理解
- 地图字符间没有空格,可以用c=cin.get()提取字符。注意须得用cin.get()消融换行符。
- 除了起点和终点外的卡片不能走。可以走地图外一圈。地图只有1到5列,可以走0和6列。
- 宽搜可以求出发点到目的地的步数,可惜问的是出发点到目的地线路变向次数,就是线段数。方向不一样要用深搜。
5. 深搜,标记并找到通往终点的线路,变向就增加。回溯找到最短的线路。格子来的方向和周围某邻格子移动方向一致,不用转向,否则转向。 - 深搜剪枝非常重要。超过已有更佳答案的停止搜索。
回溯深搜代码
#include <bits/stdc++.h>
using namespace std;
const int inf=99999;
struct point{int x,y;};//坐标和到达步数
int r,c,//行列
ans,//最少步数
z1,z2,//几组地图,每组几组数据
x2,y2,x3,y3,//出发到达坐标
d[5][2]={{0,0},{0,1},{1,0},{0,-1},{-1,0}};//一个格子往周围四个方向移动
bool k[80][80],k2[80][80],k3[80][80];//表示能不能走,也标记走过没
char cx,cc[80][80];//地图每格字符
void view(){
cout<<“显示”<<endl;
for(int i=0;i<=r+1;i++){
for(int j=0;j<=c+1;j++){
if(ix2&&jy2)cout<<“s”;
else if(ix3&&jy3)cout<<“e”;
else cout<<" “;
cout<<cc[i][j]<<k3[i][j]<<”\t";
}
cout<<endl;
}
}
void go(point p,int dx,int he){//递归,该点移动方向,到达目的地最少线段数
if(p.xx3&&p.yy3){
for(int i=0;i<=r+1;i++)for(int j=0;j<=c+1;j++)k3[i][j]=k[i][j];
ans=min(ans,he);return;//到达目的地,该次递归结束
}
if(he>=ans)return;//剪枝,如果线段树超过已有最少线段数,就停止递归
int x,y;
for(int i=1;i<=4;i++){
x=p.x+d[i][0],y=p.y+d[i][1];//四个方向移动后坐标
if((!k[x][y]||xx3&&yy3)&&x>=0&&x<=r+1&&y>=0&&y<=c+1){//新坐标在地图里,不是卡片或者到达目标
k[x][y]=1;//标记,下次不走了
if(idx||dx0)go(point{x,y},i,he);//没有转向,到达这里的线段数不变
else go(point{x,y},i,he+1);//转向了,线段数增加
k[x][y]=0;//回溯,撤销上一步
}
}
}
int main(){
freopen(“data.cpp”,“r”,stdin);
while(cin>>c>>r&&(r||c)){//列行,非零循环
cin.get();//消融上一行换行符
memset(k2,0,sizeof(k2));//标记卡片位置
for(int i=1;i<=r;i++){
for(int j=1;j<=c;j++){
cx=cin.get();cc[i][j]=cx;
if(cx==‘X’)k2[i][j]=1;//不能走
else if(cx==’ ')k2[i][j]=0; //能走
}
cin.get();//消融上一行换行符
}
cout<<“Board #”<<++z1<<“:”<<endl;
z2=1;
while(cin>>y2>>x2>>y3>>x3&&(x2||y2||x3||y3)){
for(int i=0;i<=r+1;i++)for(int j=0;j<=c+1;j++)k[i][j]=k2[i][j];//恢复地图
ans=inf;//从最大中找最少
go(point{x2,y2},0,1);//从起点回溯递归
cout<<“Pair “<<z2++<<”:”;
if(ans<inf)cout<<" “<<ans<<” segments.\n";
else cout<<" impossible.\n";
view();
}
cout<<endl;
}
return 0;
}
记忆化递归
-
存储每个格子四个方向的最佳值,不是零就直接返回,否则就需要计算,变向就是下个格子值+1,不变就不加。
-
注意跳出递归条件。
-
递归返回值就是记忆化递归,不返回就是深搜。
记忆化递归代码
#include <bits/stdc++.h>
using namespace std;
//typedef inf 0x7f7f7f7f;别名
#define inf 99 //极大值,表示到不了目的地
int r,c,//行列
ans,//到达目的地最少线条数
z1,z2,//几组地图,每组几组数据
x,y,//宽搜当前坐标
x2,y2,x3,y3,//出发到达坐标
d[5][2]={{0,0},{0,-1},{-1,0},{0,1},{1,0}},//移动方向
dn[80][80][5];//记住每个格子五个方向的到达线条数
char cx,cc[80][80];//地图便于纠误
bool k[80][80];//标记卡牌位置
void view(int x2,int y2,int x3,int y3){//标明出发结束位置
for(int i=0;i<=r+1;i++){//可以走外围一圈
for(int j=0;j<=c+1;j++){
if(ix2&&jy2)cout<<“S”;//标识出发
else if(ix3&&jy3)cout<<“E”;//目的地
else cout<<" “;
cout<<cc[i][j]<<”:“<<setw(2)<<dn[i][j][1]<<”,“<<setw(2)<<dn[i][j][2]<<”,“<<setw(2)<<dn[i][j][3]<<”,“<<setw(2)<<dn[i][j][4]<<” “;
}cout<<endl;
}
cout<<endl;
}
int go(int xx,int yx,int dx){//往同一方向走一格,四个方向得递归该邻格,得到本格和该邻格的线条数
if(dn[xx][yx][dx])return dn[xx][yx][dx];//已经算过了就不再算了
if(xxx3&&yxy3){//到达目标位置,线条数是0
for(int i=1;i<=4;i++)dn[xx][yx][i]=1;
return 1;
}
if(k[xx][yx]){//有卡片是无穷大
for(int i=1;i<=4;i++)dn[xx][yx][i]=inf;
return inf;
}
int x,y;
dn[xx][yx][dx]=inf;//找最小值,先赋最大值
x=xx+d[dx][0],y=yx+d[dx][1];//同一方向跨一格
//!!!该步已经在卡片上了,貌似不能不访问卡片
if(x>=0&&x<=r+1&&y>=0&&y<=c+1)//该格子在地图里
for(int i=1;i<=4;i++){//四个方向
dn[x][y][i]=go(x,y,i);//继续计算该邻格的线条数
if(idx)dn[xx][yx][dx]=min(dn[xx][yx][dx],dn[x][y][i]);//同向不变
else dn[xx][yx][dx]=min(dn[xx][yx][dx],dn[x][y][i]+1);//异向增1
}
return dn[xx][yx][dx];//要循环四次,找最小值,不能直接返回
}
int main(){
freopen(“data.cpp”,“r”,stdin);
while(cin>>c>>r&&(r||c)){//列行,非零循环
cin.get();//前一行输入后回车
memset(k,0,sizeof(k));//地图表示卡片
for(int i=1;i<=r;i++){
for(int j=1;j<=c;j++){
cx=cin.get();cc[i][j]=cx;
if(cx’X’)k[i][j]=1;//不能走
}
cin.get();//消除该行结束符
}
cout<<“Board #”<<++z1<<”:“<<endl;
z2=1;
while(cin>>y2>>x2>>y3>>x3&&(x2>0||y2>0||x3>0||y3>0)){//一个地图多组数据
ans=inf;
memset(dn,0,sizeof(dn));//所有格子都没算过
view(x2,y2,x3,y3);
for(int i=1;i<=4;i++){//往四个方向出发
dn[x2][y2][i]=inf;//找最小值,所以是最大值
x=x2+d[i][0],y=y2+d[i][1];//四个方向邻格
for(int j=1;j<=4;j++){//算邻格四方向
dn[x][y][j]=go(x,y,j);//递归计算
if(i==j)dn[x2][y2][i]=min(dn[x2][y2][i],dn[x][y][j]);//同向不变
else dn[x2][y2][i]=min(dn[x2][y2][i],dn[x][y][j]+1);//异向增1
}
ans=min(ans,dn[x2][y2][i]);//找最小值
}
view(x2,y2,x3,y3);
cout<<“Pair “<<z2++<<”:”;
if(ans<inf)cout<<” “<<ans+1<<” segments.\n";
else cout<<" impossible.\n";
}
cout<<endl;
}
return 0;
}
小结
先宽搜,再深搜,再记忆化递归。
- 没读透题,不够认真,错以为要求步数。
- 深搜是不返回值的递归。注意剪枝
- 记忆化递归要返回值。注意要有返回值。
- 把演示数据表现出来有助于理解。