题解 [ABC213E]Stronger Takahashi

这场比赛两题都用了复杂的做法 “山路十八弯”,C 就一离散化被我搞得那么复杂,这 E 一简单 01 bfs 又被我整了个细节巨多的做法。

\((1, 1)\) 走到 \((n, m)\),只能走在 . 上不能走在 # 上,每次可以消掉 \(2 \times 2\)#,求最少消掉几次才能到终点。

看到题就想到是某种神奇的建图方式然后给跑个最短路,然后略加思索就觉得是把每个联通块给标识出来以后在枚举每个 \(2 \times 2\) 进行爆破,看能把哪些连在一起。
写完了发现样例三这样要连续爆破的就过不了了,于是又把算法改成把单独的一个 # 也算作联通块进行处理。

可是这样一来就有大问题了,如这样:

..
##
##
##
##
##
..

若无脑把周围的都互相连上那么这本来要三次的就变成只要两次了。
而若无脑周围都不连,又会使一些两次的变成三次。

然后想来想去一直到比赛结束前最后五分钟才改出来一个连法,还自以为很妙:

  • \(2 \times 2\) 外面到里面的统统连上
  • 通向外面的 . 的连上
  • 否则一概不连

这样可以保证跨越两块的不会被过多或过少的计数。

冗长的代码
#include <iostream>
#include <vector>
#include <map>
#include <queue>
#include <algorithm>
#define mapa std::make_pair
const int N = 505;
int n, m, col[N][N], dis[N*N], tot;
bool vis[N*N];
char map[N][N];
std::vector<int> g[N*N];
std::queue<int> q;
int dx[4] = {0, 0, -1, 1},
    dy[4] = {1, -1, 0, 0};
int bx[12] = {-1, -1, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2},
    by[12] = {0, 1, -1, 0, 1, 2, -1, 0, 1, 2, 0, 1},
    bf[12] = {1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1};
void color(int x, int y, int c) {
    col[x][y] = c;
    for (int i = 0; i < 4; i++) {
        int xx = x + dx[i], yy = y + dy[i];
        if (xx < 1 || xx > n || yy < 1 || yy > m 
            || map[xx][yy] == '#' || col[xx][yy]) continue;
        color(xx, yy, c);
    }
}
void bfs(int s) {
    q.push(s); vis[1] = 1;
    while (!q.empty()) {
        int u = q.front(); q.pop();
        for (int i = 0; i < (int)g[u].size(); i++) {
            int v = g[u][i];
            if (vis[v]) continue;
            dis[v] = dis[u] + 1;
            vis[v] = 1;
            q.push(v);
        }
    }
}
int main() {
    std::cin >> n >> m;
    for (int i = 1; i <= n; i++) 
        std::cin >> (map[i] + 1);
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++) 
            if (map[i][j] == '.' && !col[i][j])
                color(i, j, ++tot);
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            if (map[i][j] == '#') col[i][j] = ++tot;
    for (int i = 1; i <= n-1; i++)
        for (int j = 1; j <= m-1; j++) {
            std::vector<std::pair<int, std::pair<bool, bool> > > tmp;
            for (int k = 0; k < 12; k++) {
                int xx = i + bx[k], yy = j + by[k];
                if (xx < 1 || xx > n || yy < 1 || yy > m)
                    continue;
                tmp.push_back(mapa(col[xx][yy], mapa(bf[k], map[xx][yy] == '#')));
            }
            for (int a = 0; a < (int)tmp.size(); a++)
                for (int b = 0; b < (int)tmp.size(); b++) {
                    int u = tmp[a].first, v = tmp[b].first;
                    if ((tmp[a].second.first && !tmp[b].second.first) || !tmp[b].second.second) 
                        g[u].push_back(v);
                }
        }
    bfs(col[1][1]);
    std::cout << dis[col[n][m]];
}

赛后看了题解。
发现这题非常简单啊!

一个点爆破一次可以到这些点

.***.
*****
**T**
*****
.***.

那么 01bfs 即可。

简洁的代码
#include <iostream>
#include <queue>
#include <utility>
#include <algorithm>
#include <cstring>
#define mapa std::make_pair
const int N = 505;
int n, m, vis[N][N], dis[N][N];
char map[N][N];
std::deque<std::pair<int, int> > q;
int dx[20] = {0, 0, 1, -1, -2, -2, -2, -1, -1, -1, -1, 0, 0, 1, 1, 1, 1, 2, 2, 2},
    dy[20] = {-1, 1, 0, 0, -1, 0, 1, -2, -1, 1, 2, -2, 2, -2, -1, 1, 2, -1, 0, 1};
bool check(int x, int y) { return x < 1 || x > n || y < 1 || y > m; }
void bfs(int x, int y) {
    q.push_back(mapa(x, y));
    memset(dis, 0x3f, sizeof dis);
    dis[x][y] = 0;
    while (!q.empty()) {
        x = q.front().first, y = q.front().second, q.pop_front();
        if (vis[x][y]) continue;
        vis[x][y] = 1;
        for (int i = 0; i < 4; i++) {
            int xx = x + dx[i], yy = y + dy[i];
            if (check(xx, yy) || map[xx][yy] == '#') continue;
            dis[xx][yy] = std::min(dis[xx][yy], dis[x][y]), q.push_front(mapa(xx, yy));
        }
        for (int i = 0; i < 20; i++) {
            int xx = x + dx[i], yy = y + dy[i];
            if (check(xx, yy)) continue;
            dis[xx][yy] = std::min(dis[xx][yy], dis[x][y]+1), q.push_back(mapa(xx, yy));
        }
    }
}
int main() {
    std::cin >> n >> m;
    for (int i = 1; i <= n; i++)
        std::cin >> (map[i]+1);
    bfs(1, 1);
    std::cout << dis[n][m];
}

又好写又跑得快。

那么问题出在哪里使我没能想到这样子的简单的做法呢?
说道底还是不愿仔细模拟和观察性质的老问题没有解决。

posted @ 2021-08-09 08:20  Acfboy  阅读(100)  评论(0编辑  收藏  举报