题解 P8169 [eJOI2021] Dungeons

· · 题解

整体想法其实很直接:

当然如果直接实现上述过程肯定写起来非常阴间,而且复杂度不一定对。

因此我们简化一下这个过程。注意到 S 一旦变化为 S',则新的 S' 一定包含于 S。因此对于 S 绝对安全的格子对于 S' 也一定绝对安全。所以可以让玩家在此时直接丢弃所有信息重新开始扩展,也可以求得答案。

从而我们实现如下过程:

最终结果就是对所有起点构成的集合进行上述过程的返回值。

直接实现,复杂度 O(nmS^2):每次 BFS 扩展复杂度 O(nmS),同时集合分裂 S 次。

这个复杂度无法通过本题。考虑到瓶颈有两个,分别是:

  1. BFS 扩展时判断一个点是否绝对安全,这可以压位:对于每一个 (x,y) 保存一个 64 位整数,第 i 位存储相对第 i 个起点的位置是 (x,y) 的位置是否安全(“安全”指不是 X#),然后就可以用一次位运算判断。
  2. 判断一个点是否可以分裂 S。这同样可以压位:一个点有三种视觉效果(障碍、空地、金币),对于每一个 (x,y) 保存三个 64 位整数,第 i 位存储相对第 i 个起点的位置是 (x,y) 的位置的视觉效果是不是障碍(空地、金币),然后就可以用三次位运算判断。

进行如上优化后,一轮过程的复杂度下降至 O(nm),总复杂度即下降至 O(nmS) 即可通过。

const int Next[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
const int Sight[9][2] = {{0, 0}, {0, 1}, {1, 0}, {0, -1}, {-1, 0}, {-1, -1}, {-1, 1}, {1, -1}, {1, 1}};
char mp[405][405], rel[60][815][815];
int n, m, x[60], y[60], s;
long long saf[815][815], sig[815][815][3];
bool vis[815][815], isg[815][815];

inline void Read() {
    cin >> n >> m;
    for (int i = 1;i <= n;i++) {
        for (int j = 1;j <= m;j++) {
            cin >> mp[i][j];
            if (mp[i][j] == 'S') {
                x[s] = i; y[s] = j;
                s++;
            }
        }
    }
}

inline void Prefix() {
    for (int k = 0;k < s;k++) {
        for (int i = 1;i <= n;i++) {
            for (int j = 1;j <= m;j++) rel[k][i - x[k] + 402][j - y[k] + 402] = mp[i][j];
        }
    }
    for (int k = 0;k < s;k++) {
        for (int i = 1;i <= 810;i++) {
            for (int j = 1;j <= 810;j++) {
                if (rel[k][i][j] == 'X' || rel[k][i][j] == '#') saf[i][j] |= (1ll << k);
                if (rel[k][i][j] == 'X' || rel[k][i][j] == '.' || rel[k][i][j] == 'S') sig[i][j][0] |= (1ll << k);
                if (rel[k][i][j] == '#') sig[i][j][1] |= (1ll << k);
                if (rel[k][i][j] == 'o') sig[i][j][2] |= (1ll << k);
            }
        }
    }
}

inline int Work(long long sta) {
    //cout << "work " << sta << endl;
    memset(vis, 0, sizeof(vis));
    memset(isg, 0, sizeof(isg));
    queue <pair <int, int> > que;
    que.push(make_pair(402, 402));
    vis[402][402] = 1;
    while (!que.empty()) {
        int x = que.front().first, y = que.front().second;
        que.pop();
        for (int k = 0;k < 4;k++) {
            int tx = x + Next[k][0], ty = y + Next[k][1];
            if (tx < 1 || tx > 810 || ty < 1 || ty > 810) continue;
            if (sta & saf[tx][ty]) continue;
            if (vis[tx][ty]) continue;
            vis[tx][ty] = 1;
            que.push(make_pair(tx, ty));
        }
    }
    for (int i = 1;i <= 810;i++) {
        for (int j = 1;j <= 810;j++) {
            if (vis[i][j]) {
                for (int k = 0;k < 9;k++) {
                    int tx = i + Sight[k][0], ty = j + Sight[k][1];
                    if (tx < 1 || tx > 810 || ty < 1 || ty > 810) continue;
                    isg[tx][ty] = 1;
                }
            }
        }
    }
    for (int i = 1;i <= 810;i++) {
        for (int j = 1;j <= 810;j++) {
            if (isg[i][j]) {
                //cout << i << " " << j << endl;
                for (int c = 0;c < 3;c++) {
                    if ((sig[i][j][c] & sta) != 0 && (sig[i][j][c] & sta) != sta) {
                        return min(Work((sig[i][j][c] & sta)), Work(sta ^ (sig[i][j][c] & sta)));
                    }
                }
            }
        }
    }
    int ans = 0;
    for (int i = 1;i <= 810;i++) {
        for (int j = 1;j <= 810;j++) {
            if (vis[i][j] && (sig[i][j][2] & sta) == sta) ans++;
        }
    }
    return ans;
}

int main() {
    Read();
    Prefix();
    //cout << "pass" << endl;
    cout << Work((1ll << s) - 1) << endl;
    return 0;
}