洛谷:P1518:两只塔姆沃斯牛


洛谷:P1518:两只塔姆沃斯牛

题目

P1518:两只塔姆沃斯牛

分析

本题是一个很有意思的模拟题。通过模拟牛(C)和农夫(F)在牧场的移动来判断它们是否以及何时能相遇。

这个牧场很小,我们可以用最传统的模拟法。

结构体

我们观察牛和农夫,他们有一些相同的地方:

  • 都有一个位置(xy
  • 都有一个移动的方向(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能显著提升可读性与可复用性。若在竞赛中需要最短代码,也可内联移动与判定逻辑,但更易出错。抽象后注意把“右转顺序”与“方向向量”声明为单一真源,避免分散定义导致不一致。

Previous Next