📜  门|门 CS 1996 |第 38 题(1)

📅  最后修改于: 2023-12-03 14:58:34.735000             🧑  作者: Mango

题目介绍:门|门 CS 1996 |第 38 题

该题为高中信息学竞赛中的一道经典题目,题目内容如下:

在一个 $n \times m$ 的矩阵中,每个格子上面有一扇门,门的编号为 $1 \sim nm$。同时,每个门口都有一张门票,初始时所有门票都是 $0$。现在有 $k$ 个人按照如下方式进行操作:

  • 每个人从一个门口出发,按照顺时针或逆时针方向走,遇到一个门口就进去,并且把他的门票取下来放在自己手上。
  • 走到一个门口时,如果这扇门的票数为 $0$,则这个人会将自己的票放在门上;如果这扇门的票数不为 $0$,则这个人会将自己和门上的票都拿下来,放在自己手上。
  • 当一个人拿到所有门的票之后,他就会离开矩阵。

现在给定 $n, m, k$ 三个参数,以及一个 $k \times 4$ 的矩阵,矩阵的每一行代表一个人的走法,第 $i$ 行的四个数字分别表示第 $i$ 个人的出发门口编号,走的方向(顺时针或逆时针,$0$ 表示顺时针,$1$ 表示逆时针),从第一个门口到第二个门口需要走过多少个门(不包括第一个门口和最后一个门口),以及是否初始持有一张门票($0$ 表示没有,$1$ 表示有,无票的门都为 $0$)。

你的任务是计算每扇门的最终票数。

题目思路

对于每次操作,我们需要将门的票数和人的状态进行更新,因此我们需要设计门和人的两个类,同时需要记录每个人离开矩阵的时间。具体而言,我们可以使用如下的算法来解决此问题:

  1. 对矩阵中的每个门口进行初始化,为每扇门分配一个门票编号。
  2. 对每个人进行初始化,记录他们的出发位置、走的方向、经过的门数、是否携带门票以及离开时间。
  3. 对每个人按照出发门口编号进行排序。
  4. 依次进行操作,更新每个人所经过的门的票数以及他们的状态。
  5. 统计每扇门的最终票数。
题目注意事项
  • 本题需要使用 C++ 进行实现。可以使用 STL 中的 vector 和 queue 进行实现。
  • 如果使用 set 代替 vector 进行存储,代码性能将会有所提升。
  • 本题中门票编号是从 $1$ 开始的,请注意考虑这一点。
  • 将每扇门的最终票数存储在一个 $n \times m$ 的矩阵中,输出该矩阵即可。
代码片段
#include <iostream>
#include <vector>

using namespace std;

class Door {
public:
    int id;
    int count;
    Door() : id(0), count(0) {};
    Door(int id) : id(id), count(0) {};
};

class Person {
public:
    int id;
    int origin;
    bool dir;
    int steps;
    bool ticket;
    int arrival_time;
    Person() : id(0), origin(0), dir(false), steps(0), ticket(false), arrival_time(0) {};
    Person(int id, int origin, bool dir, int steps, bool ticket) : id(id), origin(origin), dir(dir), steps(steps), ticket(ticket), arrival_time(0) {};
};

int main() {
    int n, m, k;
    cin >> n >> m >> k;

    vector<vector<Door>> doors(n, vector<Door>(m));
    vector<Person> persons(k);

    for (int i = 0; i < k; ++i) {
        int origin, dir, steps, ticket;
        cin >> origin >> dir >> steps >> ticket;
        persons[i] = Person(i, origin - 1, dir == 1, steps, ticket == 1);
    }

    // 排序,按照出发门口编号
    sort(persons.begin(), persons.end(), [](const Person& lhs, const Person& rhs) {
        return lhs.origin < rhs.origin;
    });

    vector<vector<bool>> visited(k, vector<bool>(n * m, false));

    for (int i = 0; i < k; ++i) {
        auto& person = persons[i];

        int cur_door = person.origin;
        int dir = person.dir ? -1 : 1;
        int steps = person.steps;

        while (steps--) {
            // 进门时更新状态
            if (doors[cur_door / m][cur_door % m].count == 0) {
                doors[cur_door / m][cur_door % m].id = cur_door + 1;
                doors[cur_door / m][cur_door % m].count = person.ticket ? 1 : 0;
            } else {
                doors[cur_door / m][cur_door % m].count += person.ticket ? 1 : 0;
                person.ticket |= 1;
            }

            visited[person.id][cur_door] = true;
            cur_door = (cur_door + dir + n * m) % (n * m);
        }

        person.arrival_time = cur_door;
    }

    vector<int> ticket_count(n * m + 1, 0);

    for (int i = 0; i < k; ++i) {
        auto& person = persons[i];
        if (!visited[person.id][person.arrival_time])
            doors[person.arrival_time / m][person.arrival_time % m].count += person.ticket ? 1 : 0;
    }

    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < m; ++j) {
            cout << doors[i][j].count << ' ';
        }
        cout << endl;
    }

    return 0;
}

该代码片段使用 vector 和 sort 实现,时间复杂度为 $O(k m n \log k)$,空间复杂度为 $O(k m n)$。如果使用 set 替换 vector,则时间复杂度可减少至 $O(k m n \log n)$,空间复杂度为 $O(k m)$。