AT_abc431_e题解

题面:

有一个\(H*W\)的网格,现在有一束光从网格第一行第一列的左边向右射进入网格,每个格子是如下类型中的一种。

  • A型:这个格子里面什么都没有。不会改变光的方向
  • B型:这个格子里面有一个从左上到右下斜向摆放的镜子(这个镜子两面都可以反光)
  • C型:这个格子里面有一个从右上到左下斜向摆放的镜子(这个镜子两面都可以反光)

现在请问,如果希望光能从网格的最后一行最后一列向右射出网格,请问最少改变多少个格子的类型。

方法:

首先我们可以在网格里进行建图。

我们把每一个格子的每个进入方向看作一个点。

每个点出度为3(这个格子有3种类型,每种类型的出度为1)

其中有两条边的权值为1(即需要改变这个格子的类型才能到达边的到达点),一条边的权值为0(即不需要改变这个格子的类型就能到达边的到达点)。

然后我们加一个点,位于\(H\)\(W+1\)列。

注意建图时如果边的到达点不在网格图和加入的点内,那么不要建这条边。

那么答案就成了从1行1列进入方向为右这个点到从H行W+1列进入方向为右这个点的最短距离。

我们可以用dijkstra

接下来,我们进行优化。

因为所有边的权值均为0或1,所以我们可以用01BFS。

就是说:原本的priority_queue优先队列每次加入元素时是\(O(\log n)\)的,但是现在边权仅为0或1,所以我们可以用deque双端队列每次加入0时从头加入,加入1时从尾加入,这样每次加入元素就是\(O(1)\)

代码:

#include <bits/stdc++.h>
#define ll long long
using namespace std;
// 偏移数组,用于记录每个0~3的方向值所对应的具体方向,也能更加容易的枚举方向。
int dirr[4] = { 0, -1, 0, 1 }; 
int dirc[4] = { 1, 0, -1, 0 };
            //  右 上 左 下
vector<char> mp[200001]; // 原地图,因为h,w不固定,所以用这种方法
struct node
{
    ll r, c, d; 
};
map<ll, vector<pair<ll, int> > > graph; // 建的图
int h, w; // 地图尺寸
void m(char op, int& r, int& c, int& dir) // 计算当进入方向为dir进入一个位于r行c列的类型为op的方格后,会到达哪个位置和会变为哪个方向。
{
    if (op == 'B')
    {
        dir = 3 - dir; // 巧妙方法
    }
    if (op == 'C')
    {
        dir = dir / 2 * 2 + (1 - dir % 2); // 巧妙方法
    }
    r += dirr[dir];
    c += dirc[dir];
}
deque<ll> dq; // 双端队列
unordered_map<ll, int> dis; // dijkstra专用的dis数组
unordered_map<ll, bool> vis; // dijkstra专用的vis数组
void Push(ll p, int t) // 双端队列插入。
{
    if (t == 0) dq.push_front(p);
    else dq.push_back(p);
}
ll genkey(node t) // 对于每个点,生成出一个专属key
{
    return ((t.r * 1ll) << 25) | ((t.c * 1ll) << 5) | t.d;
}
node getnum(ll key) // 对于key,计算出点的各个信息
{
    node res;
    res.d = key & 0x1F;
    res.c = (key >> 5) & 0xFFFFF;
    res.r = (key >> 25);
    return res;
}
int dijkstra() // 计算最短路
{
    ll tmp = genkey({1, 1, 0});
    dq.push_front(tmp);
    dis[tmp] = 0;
    while (!dq.empty())
    {
        ll t = dq.front();
        dq.pop_front();
        if (vis[t])
            continue;
        vis[t] = true;
        for (pair<ll, int> i : graph[t])
        {
            ll v = i.first;
            int l = i.second;
            if (dis[v] > dis[t] + l)
            {
                dis[v] = dis[t] + l;
                Push(v, l);
            }
        }
    }
    return dis[genkey({h, w + 1, 0})];
}
void solve()
{
    dq.clear(); // 初始化
    cin >> h >> w;
    graph.clear(); // 初始化
    for (int i = 1; i <= h; i++)
    {
        for (int j = 1; j <= w + 1; j++)
        {
            for (int d = 0; d < 4; d++)
            {
                ll tmp = genkey({i, j, d}); // 初始化
                dis[tmp] = 1e9;             // 初始化
                vis[tmp] = false;           // 初始化
            }
        }
    }
    for (int i = 1; i <= h; i++)
    {
        mp[i].push_back(0); // 先放填掉位置0
        for (int j = 1; j <= w; j++)
        {
            char c;
            cin >> c; 
            mp[i].push_back(c); // 放入表格
            for (int d = 0; d < 4; d++) // 考虑四个节点边权为0的四个边。
            {
                int nr = i, nc = j; 
                int td = d; // 记录d的当前值
                m(c, nr, nc, d); // 计算到达点
                if (nr >= 1 && nc >= 1 && nr <= h && nc <= w + (nr == h)) // 保证在表格和加入点内
                {
                    graph[genkey({i, j, td})].push_back({genkey({nr, nc, d}), 0}); // 建边
                }
                d = td; // 不能把d改了
            }
            for (int ch = 'A'; ch <= 'C'; ch++) // 考虑四个节点边权为1的八个边
            {
                if (ch == c) continue; // 不要多建
                for (int d = 0; d < 4; d++) // 这个循环同上
                {
                    int nr = i, nc = j;
                    int td = d;
                    m(ch, nr, nc, d);
                    if (nr >= 1 && nc >= 1 && nr <= h && nc <= w + (nr == h))
                    {
                        graph[genkey({i, j, td})].push_back({genkey({nr, nc, d}), 1});
                    }
                    d = td;
                }
            }
        }
    }
    cout << dijkstra() << '\n'; // 最后输出,记得用'\n'
}
int main()
{
    // 快读
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    // 多测
    int t;
    cin >> t;
    while (t--)
    {
        solve();
    }
    return 0;
}


完结撒花❀❀.. ❀

posted @ 2025-11-20 20:02  MichaelZeng  阅读(12)  评论(0)    收藏  举报