AT_abc413_g 题解

这道题其实没有想象中那么难,赛后花了1h+ AC掉了,赛事没看(T^T)

题目传送门

vjudge AtCoder413 G - Big Banned Grid (+中文翻译)
AtCoder 413 G - Big Banned Grid

题面

有一个 \(H*W\) 的巨大网格,上面有 \(K\) 个障碍物,高桥在上面按照以下规则从 \((1,1)\) 走路 \((H, W)\)

  • 高桥每次可以往上下左右任意一个方向走动,但是不能出界也不能穿过障碍物。

请问高桥能否从 \((1,1)\) 成功抵达 \((H,W)\)

温馨提示:
\(1 \leq H,W \leq 2*10^5\)
\(0 \leq K \leq 2*10^5\)

思路

看到 \(H,W \leq 2*10^5\) 就知道没法DFS / BFS,地图都存不了。

但是 \(K \leq 2*10^5\)\(K\)不大!

继续观察:发现如果 \(K\) 个障碍中的某几个形成了一堵很长的墙,将高桥堵住了,那么高桥就不可以到达 \((H,W)\) 了。
继续观察:发现这堵墙一定是从左边/下面 延伸到 右边/上面,不然堵不住!

那么这道题就好办了,对于每一个障碍,如果它是靠着左边/下面,那么标记为1,如果它靠着右边/上面,那么标记为2,然后任何一个标记为1的障碍若可以连通到标记为2的障碍,那么就形成了一堵可以阻挡高桥的旅行的长墙!

那怎么看连通呢?

利用map记住每一个障碍的输入的下标位置,然后用iterator遍历一遍这个map,然后从iterator->first对应的位置向周围看有没有与这个障碍连通的,如果有,就用并查集合并成同一堵墙。
最后看有没有一堵墙既有标记为1的点,又有标记为2的点。

注意:
1、因为高桥只能上下左右走,不能斜线走,所以墙只要八连通就可以了,不需要四连通。
2、如果\(H = 1\) 或者 \(W = 1\) ,那么1个障碍可以同时靠着左边/下面和右边/上面,直接成为一堵堵住高桥的墙。

代码:

#include <bits/stdc++.h>
using namespace std;
map<pair<int, int>, int> mp;
int dr[8] = { -1, -1, -1, 0, 0, 1, 1, 1 }; // 八个方向
int dc[8] = { -1, 0, 1, -1, 1, -1, 0, 1 }; // 八个方向
int fa[200001];  // 并查集的fa数组
int flag[200001];// 每一个点的标记
pair<int, int> cnt[200001]; // 统计每一堵墙的两个标记的数量
int findfa(int a) // 并查集找根
{
    if (fa[a] == a)
    {
        return a;
    }
    fa[a] = findfa(fa[a]);
    return fa[a];
}
void Union(int a, int b) // 并查集合并
{
    int afa = findfa(a);
    int bfa = findfa(b);
    fa[afa] = bfa;
}
int main()
{
    int h, w, k;
    cin >> h >> w >> k;
    for (int i = 1; i <= k; i++)
    {
        fa[i] = i; // 别忘了给 fa 初始化
        int r, c;
        cin >> r >> c;
        mp[ { r, c } ] = i; // 记住该障碍的下标
        if (r == h || c == 1) // 在左边/下面
        {
            flag[i] = 1;
        }
        if (r == 1 || c == w) // 在右边/上面
        {
            flag[i] = 2;
        }
        if ((r == h || c == 1) && (r == 1 || c == w)) // 两个都是
        {
            flag[i] = 3;
        }
    }
    for (map<pair<int, int>, int>::iterator it = mp.begin(); it != mp.end(); it++) // 对于每一个障碍
    {
        int r = it->first.first;
        int c = it->first.second;
        int id = it->second;
        for (int i = 0; i < 8; i++) // 找八连通的点
        {
            int nr = r + dr[i];
            int nc = c + dc[i];
            if (mp.count( { nr, nc } )) // 如果存在
            {
                int nid = mp[ { nr, nc } ];
                Union(id, nid); // 合并成同一个墙
            }
        }
    }
    for (int i = 1; i <= k; i++)
    {
        int f = findfa(i); // 只要对并查集中的该集的根进行操作即可,因为根代表着整个集合
        cnt[f].first += (flag[i] == 1 || flag[i] == 3);
        cnt[f].second += (flag[i] == 2 || flag[i] == 3);
    }
    for (int i = 1; i <= k; i++)
    {
        if (cnt[i].first && cnt[i].second) // 如果形成了一堵贯穿左/下和右/上的长墙,那么高桥就无法通过
        {
            cout << "No" << endl;
            return 0;
        }
    }
    cout << "Yes" << endl; // 否则高桥就可以通过!
    return 0;
}

posted @ 2025-07-08 16:40  MichaelZeng  阅读(5)  评论(0)    收藏  举报