P1518 [USACO2.4] 两只塔姆沃斯牛 The Tamworth Two
前言
国庆节
今天是祖国74周岁生日!祝祖国母亲生日快乐,更加繁荣昌盛!
国庆节不忘做题,值得表扬!
也祝大家国庆节快乐
一些感想
又是一看感觉很难,然后实际确只有入门难度的题。这也许也是我的实力导致,之前实力不足,有一定的自卑心理。导致现在虽然会这题,但是也会有一个先入为主的畏难,把题目想得太难,没有在战略上藐视敌人。不过也希望通过我的努力,提高我的实力,至少提升我对待题目的心态吧。
题目
题目描述
两只牛逃跑到了森林里。Farmer John 开始用他的专家技术追捕这两头牛。你的任务是模拟他们的行为(牛和 John)。
追击在 10 × 10 10 \times 10 10×10 的平面网格内进行。一个格子可以是:一个障碍物,两头牛(它们总在一起),或者 Farmer John。两头牛和 Farmer John 可以在同一个格子内(当他们相遇时),但是他们都不能进入有障碍的格子。
一个格子可以是:
.
空地;*
障碍物;C
两头牛;F
Farmer John。
这里有一个地图的例子:
*...*.....
......*...
...*...*..
..........
...*.F....
*.....*...
...*......
..C......*
...*.*....
.*.*......
牛在地图里以固定的方式游荡。每分钟,它们可以向前移动或是转弯。如果前方无障碍(地图边沿也是障碍),它们会按照原来的方向前进一步。否则它们会用这一分钟顺时针转 90 度。 同时,它们不会离开地图。
Farmer John 深知牛的移动方法,他也这么移动。
每次(每分钟)Farmer John 和两头牛的移动是同时的。如果他们在移动的时候穿过对方,但是没有在同一格相遇,我们不认为他们相遇了。当他们在某分钟末在某格子相遇,那么追捕结束。
读入十行表示地图。每行都只包含 10 个字符,表示的含义和上面所说的相同。保证地图中只有一个 F
和一个 C
。F
和 C
一开始不会处于同一个格子中。
计算 Farmer John 需要多少分钟来抓住他的牛,假设牛和 Farmer John 一开始的行动方向都是正北(即上)。 如果 John 和牛永远不会相遇,输出 0。
输入格式
输入共十行,每行 10 个字符,表示如上文描述的地图。
输出格式
输出一个数字,表示 John 需要多少时间才能抓住牛们。如果 John 无法抓住牛,则输出 0。
样例 #1
样例输入 #1
*...*.....
......*...
...*...*..
..........
...*.F....
*.....*...
...*......
..C......*
...*.*....
.*.*......
样例输出 #1
49
提示
翻译来自NOCOW
USACO 2.4
题目分析
一开始看到这种给个图的就很容易联系到搜索,深搜啊,广搜啊。学过数据结构和算法的同学肯定不陌生。我的深搜也不是很好,所以一看到就有点畏难,但是没有读题,上机题很大一部分题是需要我们先把题目简化模拟成一个计算机可以解决的问题的。
所以我们就转化一下,实际上就是两个有固定方式的物体是否可以相遇的问题,相遇就是坐标重合,而由于他们的行走方式都是固定不变的,所以大框架应该是一个循环,循环里面就是每次一样的方式,循环终止就是遇到了或者说肯定遇不到了。
遇到好说嘛,就是坐标重合,农民和牛的坐标x和y都相等。那没办法相遇怎么判断?①首先想到就是搜索嘛,但是搜索不太会,而且搜索的行走是随机的,而本题是固定的,也不知道会不会有什么不同,所以就不用搜索。②我们用的是循环,一直没有找到相遇的状态也就可以说明是无法遇到了。但是这个要大于多少次才可以界定为失败很难说。③方法二显得很low,而且得亏是10×10,万一是1000×1000,或者一万以上,这个数值有可能会界定得过小而影响结果,而且这个方法一看就不专业。我又想到一个办法,给每个状态都设置一个哈希值(就是几乎独一无二的值,状态和数值一一对应,如果该值重复出现,说明该状态又出现过,说明是死循环了)
一看就是好方法,那么现在就是要给出这个哈希值的计算方式,状态包括六个值(农民的坐标x和y,方向dir;牛的坐标x,y,dir)。我们要根据这六个值给出一个计算公式使得每个值不会有重复的状态,而且需要尽可能小以节省空间。所以坐标有十个可能,方向只有四个选择,所以最后选择的公式是
int Hash(coordinate a,coordinate b){
return a.x+a.y*10+b.x*100+b.y*1000+a.dir*10000+b.dir*40000;
}
至于为什么是最小的,大家可以自己思考一下,至此整体构思基本结束。
注意事项
这个注意事项还挺多的,应该我也是错了两次才做出来
1.只有一头牛,一个 C
,虽然题目最开始说的是两头,但是样例只给了一头。
2.转向也需要花费时间,所以转向后就不能前进了
3.注意初始的方向,矩阵的x和y跟坐标系的x和y不同,注意区分。
4.hash数组记得更新,有过的状态就标记为1,新的状态发现是1,说明已经有经历过,说明无解
代码
三次提交终于拿下,心急吃不了热豆腐。
完整代码如下:
#include<iostream>
using namespace std;
//0代表平地,1代表障碍,2代表农民,3代表牛
int map[15][15]={0};
int dx[4]={-1,0,1,0};
int dy[4]={0,1,0,-1};//按照顺时针方向
struct coordinate{
int x,y,dir=0;
}farmer,cow;
int hashi[270007]={0};//记录哈希值
int Hash(coordinate a,coordinate b){
return a.x+a.y*10+b.x*100+b.y*1000+a.dir*10000+b.dir*40000;
}
int main()
{
int count=0,now;//步数统计、目前状态
char charater;
for(int i = 0; i<12;i++){
for(int j = 0;j<12;j++){
map[i][j]=1;}}
for(int i = 1; i<11;i++){
for(int j = 1;j<11;j++){
cin>>charater;
if(charater=='*')
map[i][j]=1;
else{//都不是障碍物
map[i][j]=0;
if(charater=='F'){
farmer.x=i;
farmer.y=j;
}
else if(charater=='C'){
cow.x=i;
cow.y=j;
}
}
}
}
int temp;//来保存暂时的方向(不然方向有点长)
//第一种撞到输出,第二种哈希相同
while(!(farmer.x==cow.x&&farmer.y==cow.y)&&hashi[now]==0){
count++;//又过了一分钟
now=Hash(farmer,cow);
hashi[now]=1;
temp=farmer.dir;
if(map[farmer.x+dx[temp]][farmer.y+dy[temp]]==1){//遇到障碍转弯
farmer.dir++;//转弯turn right
farmer.dir%=4;
}
else{
farmer.x+=dx[temp];
farmer.y+=dy[temp];
}
temp=cow.dir;
if(map[cow.x+dx[temp]][cow.y+dy[temp]]==1){//遇到障碍转弯
cow.dir++;//转弯turn right
cow.dir%=4;
}
else{
cow.x+=dx[temp];
cow.y+=dy[temp];
}
now=Hash(farmer,cow); //更新状态
}
if(hashi[now]==1)
cout<<0;
else
cout<<count;
return 0;
}
后话
一些有趣的插曲
1.给我的cpp文件命名时感觉原题不能表现它的题意,于是我想改成“John找牛”,不知道输入法为什么变成“John找妞”。这肯定不是我的问题。我是一个多么正直的人粉丝们都知道。我猜测是输入法思想的问题或者它认为John需要找的是妞而不是牛。
2.犯了两个低级错误,第一次方向和坐标系的弄混了,第二次忘记考虑失败情况的输出,导致平白无故多了两次提交。
3.感觉还可以改进,在循环部分的hash值计算更新用了两次,我觉得也许只需要一次,但是没想到方法,希望大家看完后可以提一点建议。
一些额外的测试用例
因为没过,所以下载了一些测试用例,供大家测试和找自己的错误用。
样例输入 #1
.****...*.
..*......*
*.........
..........
*........*
*.**.*..**
F..*......
***....*.*
.C.......*
.......*.*
样例输出 #1
58
样例输入 #2
..........
.********.
.********.
.********.
.********.
.********.
.********.
C********.
.********.
F.........
样例输出 #2
0