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;
}
完结撒花❀❀.. ❀❀

浙公网安备 33010602011771号