病毒在封闭空间中的传播时间

131 阅读3分钟

题目链接www.marscode.cn/practice/w7…

题目中的seats表示所有人的戴口罩情况,若seats[r][c] == 1,则表示rc列的那个人戴了口罩,若seats[r][c] == 0,则表示rc列的那个人没戴口罩。patient[0]patient[1]分别表示初始患者的行、列坐标。“邻座”的含义是前后左右的座位。

Dijkstra 算法 (贪心)

代码

时间复杂度: O((n×m)log(n×m))O\bigl((n \times m) \log (n \times m)\bigr)infectedTime数组的初始化的时间复杂度为O(n×m)O\bigl(n \times m\bigr)Dijkstra 算法的时间复杂度为O((n×m)log(n×m))O\bigl((n \times m) \log (n \times m)\bigr)

空间复杂度: O(m×n)O(m\times n)infectedTime数组的空间复杂度为O(m×n)O(m\times n)。一个位置的感染时间虽然有可能被多次更新,但是更新仅发生于一个已经确定感染时间的邻居尝试感染周围人时。由于每个位置最多有 4 个邻居,因此总的更新次数上限是O(4×m×n)O(4\times m\times n)

#include <iostream>
#include <vector>
#include <queue>
#include <limits>
#include <algorithm>
#include <array>
using namespace std;

static constexpr int NOT_INFECTED = numeric_limits<int>::max();

struct InfectionState {
    int time;
    int row;
    int col;

    bool operator>(const InfectionState &other) const {
        return time > other.time;
    }
};

int solution(int rowCount, int columnCount, vector<vector<int> > seats, vector<int> patient) {
    const int patientRow = patient[0];
    const int patientCol = patient[1];

    auto inBound = [&rowCount, &columnCount](const int row, const int col)-> bool {
        return row >= 0 and row < rowCount and col >= 0 and col < columnCount;
    };

    if (!inBound(patientRow, patientCol)) {
        return 0;
    }

    vector<vector<int> > infectedTime(rowCount, vector<int>(columnCount, NOT_INFECTED));
    infectedTime[patientRow][patientCol] = 0;

    priority_queue<InfectionState, vector<InfectionState>, greater<> > minHeap;
    minHeap.push({0, patientRow, patientCol});

    static const array<pair<int, int>, 4> directions = {{{-1, 0}, {1, 0}, {0, -1}, {0, 1}}};

    auto countInfectedNeighborsAtOrBefore = [&](const int r, const int c, const int t)-> int {
        int infectedCount = 0;
        for (const auto &[dr, dc]: directions) {
            const int nr = r + dr;
            const int nc = c + dc;
            if (inBound(nr, nc) and infectedTime[nr][nc] <= t) {
                ++infectedCount;
            }
        }
        return infectedCount;
    };

    int maxTime = 0;
    while (!minHeap.empty()) {
        InfectionState current = minHeap.top();
        minHeap.pop();

        if (current.time > infectedTime[current.row][current.col]) {
            continue;
        }
        maxTime = max(maxTime, current.time);

        for (const auto &[dr, dc]: directions) {
            const int newRow = current.row + dr;
            const int newCol = current.col + dc;
            if (!inBound(newRow, newCol)) {
                continue;
            }

            const int neighborMask = seats[newRow][newCol];
            int candidateTime;
            if (neighborMask == 0) {
                candidateTime = current.time + 1;
            } else {
                const int infectedNeighborCount = countInfectedNeighborsAtOrBefore(newRow, newCol, current.time);
                if (infectedNeighborCount >= 2) {
                    candidateTime = current.time + 1;
                } else {
                    candidateTime = current.time + 2;
                }
            }

            if (candidateTime < infectedTime[newRow][newCol]) {
                infectedTime[newRow][newCol] = candidateTime;
                minHeap.push({candidateTime, newRow, newCol});
            }
        }
    }

    return maxTime;
}

int main() {
    vector<vector<int> > testSeats1 = {
        {0, 1, 1, 1},
        {1, 0, 1, 0},
        {1, 1, 1, 1},
        {0, 0, 0, 1}
    };

    vector<vector<int> > testSeats2 = {
        {0, 1, 1, 1},
        {1, 0, 1, 0},
        {1, 1, 1, 1},
        {0, 0, 0, 1}
    };

    vector<vector<int> > testSeats3 = {
        {0, 0, 0, 0},
        {0, 0, 0, 0},
        {0, 0, 0, 0},
        {0, 0, 0, 0}
    };

    vector<vector<int> > testSeats4 = {
        {1, 1, 1, 1},
        {1, 1, 1, 1},
        {1, 1, 1, 1},
        {1, 1, 1, 1}
    };

    vector<vector<int> > testSeats5 = {
        {1}
    };

    cout << (solution(4, 4, testSeats1, {2, 2}) == 6) << endl;
    cout << (solution(4, 4, testSeats2, {2, 5}) == 0) << endl;
    cout << (solution(4, 4, testSeats3, {2, 2}) == 4) << endl;
    cout << (solution(4, 4, testSeats4, {2, 2}) == 6) << endl;
    cout << (solution(1, 1, testSeats5, {0, 0}) == 0) << endl;

    return 0;
}

思路

  1. 维护一个 infectedTime\text{infectedTime} 数组。其中 infectedTime[r][c]\text{infectedTime}[r][c] 表示坐在 (r,c)(r, c) 位置的人最早在第几秒被感染。初始时,除患者外都设为“未感染”(用23112^{31}-1来表示),初始患者本人的最早感染时间设为0。

  2. 使用最小堆按时间升序处理感染

    1. 堆中存储三元组 (t,r,c)(t, r, c),表示位置 (r,c)(r, c) 最早在时间 tt 被感染。
    2. 每次从堆顶取出当前时间最小的条目 (t,r,c)(t, r, c)。若 tt 大于 infectedTime[r][c]\text{infectedTime}[r][c],说明已经有更早的方式感染了该位置,则跳过。
    3. 否则,枚举它的四个邻座 (nr,nc)(nr, nc),计算该邻座基于当前时刻 tt可能感染时间。若该时间早于 infectedTime[nr][nc]\text{infectedTime}[nr][nc],则更新并推入堆。
  3. 传染规则的实现

    1. 若邻座未戴口罩(seats[nr][nc]=0seats[nr][nc] = 0),只需 1 秒即可感染。

    2. 若邻座戴口罩(seats[nr][nc]=1seats[nr][nc] = 1),则需要判断在时刻 tt 之前或正好 tt 时刻有多少个邻座已被感染:

      • 若已感染邻居数 2\geq 2,则只需 1 秒即可感染。
      • 否则,需要 2 秒感染。
  4. 在所有可感染的位置都更新完毕后,infectedTime\text{infectedTime} 数组中最大的那个值就是感染全场所用的最短时间。