题目
分析
本题是一个很有意思的模拟题。通过模拟牛(C)和农夫(F)在牧场的移动来判断它们是否以及何时能相遇。
这个牧场很小,我们可以用最传统的模拟法。
结构体
我们观察牛和农夫,他们有一些相同的地方:
- 都有一个位置(
x和y) - 都有一个移动的方向(
direction) - 都要进行移动的动作,而且移动规则一样:不能越界。如果碰到障碍(
*)就向右转,否则就前进一步。
这种有类似行为的实体,我们可以进行一次抽象,将相同的地方抽象出来而用结构(或者更高级的类)表示:
struct Entity
{
int x, y; // Tracks the position
int direction; // Tracks the diretion to move
void move()
{
int next_x = x + steps[direction][0];
int next_y = y + steps[direction][1];
if (legal_move(next_x, next_y))
{
x = next_x;
y = next_y;
}
else
{
direction = (direction + 1) % 4;
}
}
};
可以说,不管是牛还是农夫,我们将他们的“性质”和“行为”都抽象成了一个Entity。本题的核心逻辑也实现在了这个结构中,尤其是其中判定下一步移动是否合法(legal_move)(并移动)的操作和转向(但不移动)的操作。
这里我们再次看到如何高效地表示四个方向。需要特别注意的是,这四个方向必须严格按照右转的顺序排列:上-右-下-左。否则我们上面代码中direction = (direction + 1) % 4;的计算就完全错了。
int steps[4][2] = {
{-1, 0},
{0, 1},
{1, 0},
{0, -1}
};
合法移动的判定
按照题意每过一分钟,农夫和奶牛都会试着按照当前的方向走一步。那么他们的下一个落点是不是合法呢?我们先看不合法的落点:
- 超出边界
- 碰到障碍(
*)
所以,我们对下一步移动是否合法的判定也可以用一个函数来处理:
bool legal_move(int x, int y)
{
if (x < 0 || y < 0 || x >= N || y >= N || maps[x][y] == '*')
{
return false;
}
else
{
return true;
}
}
这个函数在结构Entity中的move()函数中会被调用,表示每进行一次移动,都要判定一次。然后move函数根据是否合法移动,采取不同的处理。
牧场设置的输入
在主程序中我们要完成牧场设置的输入和处理。这里需要注意的,是对F|C的位置的处理。
如果输入的字符是F或者C,那么我们需要记录农夫和奶牛的原始位置。记录好位置后,这两个原始位置其实是可以被经过的。但是我们在上面的legal_move中,只检查maps[x][y] == '*',换句话说,即使某个位置的字符是F|C,我们也不用担心判定出错。
不过实际处理中,为了统一,我们还是将农夫和奶牛的位置设置为'.'。
for (int i = 0; i < N; i++)
{
for (int j = 0; j < N; j++)
{
char c;
cin >> c;
if (c == 'F') // Farmer here
{
farmer.x = i;
farmer.y = j;
c = '.';
}
else if (c == 'C') // Cow here
{
cow.x = i;
cow.y = j;
c = '.';
}
maps[i][j] = c;
}
}
是否能碰到的判定
我们不知道奶牛和农夫是否会碰到,也不知道经过几步之后两者会碰到。对于这种情况,在竞赛题中,常见的做法是设定一个很大很大的上限,比如max_loop=15000。每次进行移动的时候,增加记录当前步数的变量(ans),如果\(ans \gt max\_loop\),也就是经过了15000次的移动,在这个10*10的牧场内,农夫和奶牛还是从未碰头,那么我们可以比较安全地认为两者就永远不会碰头了。
答案

思考
状态空间与循环检测
本题的核心是两个实体的同步移动与右转规则,会导致状态空间有限(位置×方向,共\((10×10×4)^2=160,000\)种组合),因此必然进入循环。设置 max_loop=15000 是经验做法,更稳妥的是用哈希或布尔数组记录状态 (cow.x,cow.y,cow.dir,farmer.x,farmer.y,farmer.dir),一旦重复即判定永不相遇。当然,本题中15000已经够好了,因为代码已经AC。
就地与抽象的取舍
将“位置+方向+移动规则”抽象为Entity能显著提升可读性与可复用性。若在竞赛中需要最短代码,也可内联移动与判定逻辑,但更易出错。抽象后注意把“右转顺序”与“方向向量”声明为单一真源,避免分散定义导致不一致。
