A - Maze
题意
东东有一张地图,想通过地图找到妹纸。地图显示,0表示可以走,1表示不可以走,左上角是入口,右下角是妹纸,这两个位置保证为0。既然已经知道了地图,那么东东找到妹纸就不难了,请你编一个程序,写出东东找到妹纸的最短路线。
思路:
标准的广度优先搜索。
设计了迷宫单元结构体unit,包含成员数据坐标x
、y
,父坐标px
、py
,不可通行标记block
,已到达标记reach
;重载了赋值运算符。
准备了方向数组dx
、dy
,开辟了全局二维unit数组maze
。为了便于程序识别迷宫边界,将迷宫起点平移至(1,1),代码内始终如此偏移,主函数中将迷宫边界的block
全部置1。
设计了广度优先搜索bfs()
,借助队列实现。循环在队列清空前始终继续,每次取出队列头检查四个方向并将未抵达的可通行方向加入队列,并更新该方向的父节点,直到到达终点。
设计了寻路函数way()
,借助栈实现,bfs()
运行完后即从终点开始回溯父节点,不断将父节点压入栈直到到达起点。
输出函数从栈中取出走过的路径坐标,恢复偏移后按格式输出。
总结:
巩固了广度优先搜索的知识,练习了bfs的应用及代码书写中的一些技巧,如建立方向数组以精简代码等。
代码:
#include<iostream>
#include<stack>
#include<queue>
using namespace std;
struct unit{//地图单元
int x,y;//坐标
int px,py;//父坐标
bool block;//不可通行标记
bool reach;//已到达标记
void operator=(unit& u){
x=u.x;
y=u.y;
px=u.px;
py=u.py;
block=u.block;
reach=u.reach;
}
};
//方向数组
int dx[]={0,0,1,-1};
int dy[]={1,-1,0,0};
unit maze[8][8];
//为便于程序书写 代码内将迷宫起点平移至(1,1)
queue<unit> Q;
void bfs(){
Q.push(maze[1][1]);
while(!Q.empty()){//队列清空前始终循环
unit thisOne=Q.front();
Q.pop();
int x=thisOne.x;
int y=thisOne.y;
for(int i=0;i<4;i++){//检查四个方向并入队
if(!maze[x+dx[i]][y+dy[i]].block){
maze[x+dx[i]][y+dy[i]].px=x;
maze[x+dx[i]][y+dy[i]].py=y;
if(x+dx[i]==5&&y+dy[i]==5) return;
maze[x+dx[i]][y+dy[i]].block=1;
Q.push(maze[x+dx[i]][y+dy[i]]);
}
}
}
}
stack<unit> S;
int way(){//从终点开始将父节点压入栈直到起点
int x,y,x2,y2;
int cot=0;
x=y=5;
while(x!=1||y!=1){
S.push(maze[x][y]);
x2=maze[x][y].px;
y2=maze[x][y].py;
x=x2;
y=y2;
cot++;
}
S.push(maze[1][1]);
return cot;
}
void output(){//输出坐标(恢复偏移)
int x,y;
unit U;
while(!S.empty()){
U=S.top();
S.pop();
x=U.x;
y=U.y;
cout<<"("<<x-1<<", "<<y-1<<")"<<endl;
}
}
int main(){
for(int i=0;i<=6;i++)//为迷宫边界block置1
maze[i][0].block=maze[0][i].block=maze[i][6].block=maze[6][i].block=1;
for(int i=1;i<=5;i++){//输入迷宫
for(int j=1;j<=5;j++){
cin>>maze[i][j].block;
maze[i][j].x=i;
maze[i][j].y=j;
}
}
bfs();
way();
output();
}
B - Pour Water
题意
倒水问题 “fill A” 表示倒满A杯,"empty A"表示倒空A杯,“pour A B” 表示把A的水倒到B杯并且把B杯倒满或A倒空。
输入包含多组数据。每组数据输入 A, B, C 数据范围 0 < A <= B 、C <= B <=1000 、A和B互质。
你的程序的输出将由一系列的指令组成。这些输出行将导致任何一个罐子正好包含C单位的水。
思路:
本题是对隐式图搜索的练习。
题目可以转化为如下隐式图:已知起点为(A,B)=(0,0)
,终点为(A,B)=(0,C)
或(A,B)=(C,0)
(若C≤A)。结点间的相邻关系即fill、empty、pour三组操作所能产生的改变。
设计了结构体状态status
,含有成员变量A
、B
状态,pA
、pB
父状态,pO
父状态变为此状态所做的操作,reach
到达标记。
设计了非成员函数fill()
、empty()
、pour()
,三个函数所需参数相同,需要当前状态int a
、int b
,操作类型int ope
,引用int& x
、int& y
传出目标状态;都返回布尔值判断目标结点是否已到达。
bfs、寻路、输出过程与上一题相似。
总结&疑问:
本题联系了广度优先搜索中的一种特殊类型隐式图,节点并非由给出的边连接而是根据给出的关系在搜索过程中显露出来。本题依然穷举了所有的状态,开辟为数组并储存,但本题数据范围较小可如此操作,若隐式图节点无法穷举,应该如何解决?
代码:
#include<iostream>
#include<queue>
#include<stack>
using namespace std;
int cubageA,cubageB,C;
struct status{//隐式图节点-状态
int A,B;// 状态(坐标)
int pA,pB,pO;// 父状态(父坐标)
bool reach;//到达标记
status(){
A=B=0;
pA=pB=pO=0;
reach=0;
}
void set(int a,int b,int pa,int pb,int po){//设置节点状态
A=a;
B=b;
pA=pa;
pB=pb;
pO=po;
reach=1;
}
void clean(){//重置
A=B=pA=pB=pO=0;
reach=0;
}
};
status R[1050][1050];//枚举全部状态
bool fill(int a,int b,int ope,int&x,int&y){//fill操作:a、b为当前状态,ope表示fillA/fillB,x、y传出目标状态
int toA,toB,to;
if(ope==1){//fillA
toA=cubageA;
toB=b;
to=1;
}
else if(ope==2){//fillB
toA=a;
toB=cubageB;
to=2;
}
if(R[toA][toB].reach)return 0;//返回是否已到达,为bfs函数分支给出判断;下同理
R[toA][toB].set(toA,toB,a,b,to);
x=toA;
y=toB;
return 1;
}
bool empty(int a,int b,int ope,int&x,int&y){//empty操作:a、b为当前状态,ope表示emptyA/emptyB,x、y传出目标状态
int toA,toB,to;
if(ope==1){
toA=0;
toB=b;
to=3;
}
else if(ope==2){
toA=a;
toB=0;
to=4;
}
if(R[toA][toB].reach)return 0;
R[toA][toB].set(toA,toB,a,b,to);
x=toA;
y=toB;
return 1;
}
bool pour(int a,int b,int ope,int&x,int&y){//pour操作:a、b为当前状态,ope表示pourAB/pourBA,x、y传出目标状态
int toA,toB,to;
if(ope==1){
if(a<=(cubageB-b)){
toB=a+b;
toA=0;
}
else {
toB=cubageB;
toA=a-cubageB+b;
}
to=5;
}
else if(ope==2){
if(b<=(cubageA-a)){
toA=a+b;
toB=0;
}
else {
toA=cubageA;
toB=b-cubageA+a;
}
to=6;
}
if(R[toA][toB].reach)return 0;
R[toA][toB].set(toA,toB,a,b,to);
x=toA;
y=toB;
return 1;
}
void clear(int a,int b){//清空
for(int i=0;i<=a+5;i++){
for(int j=0;j<=b+5;j++){
R[i][j].clean();
}
}
}
queue<status*> Q;
status* bfs(){
status* temp=&R[0][0];
int a,b;
Q.push(temp);
while(!Q.empty()){
temp=Q.front();
Q.pop();
if(temp->A==C||temp->B==C)return temp;
//生成六种变化的方向,并判断是否要加入队列
if(fill(temp->A,temp->B,1,a,b))Q.push(&R[a][b]);
if(fill(temp->A,temp->B,2,a,b))Q.push(&R[a][b]);
if(empty(temp->A,temp->B,1,a,b))Q.push(&R[a][b]);
if(empty(temp->A,temp->B,2,a,b))Q.push(&R[a][b]);
if(pour(temp->A,temp->B,1,a,b))Q.push(&R[a][b]);
if(pour(temp->A,temp->B,2,a,b))Q.push(&R[a][b]);
}
}
stack<int> S;
void way(status* ans){//回溯父状态,生成路径
while(ans->pO!=0){
S.push(ans->pO);
ans=&R[ans->pA][ans->pB];
}
}
void output(){//取出栈,依照格式输出
int ope;
while(!S.empty()){
ope=S.top();
S.pop();
if(ope==1)cout<<"fill A"<<endl;
else if(ope==2)cout<<"fill B"<<endl;
else if(ope==3)cout<<"empty A"<<endl;
else if(ope==4)cout<<"empty B"<<endl;
else if(ope==5)cout<<"pour A B"<<endl;
else if(ope==6)cout<<"pour B A"<<endl;
}
cout<<"success"<<endl;
}
int main(){
while(cin>>cubageA){
cin>>cubageB>>C;
clear(cubageA,cubageB);
R[0][0].reach=1;
status* ans=bfs();
way(ans);
output();
}
}