P3956 棋盘 题解
棋盘
题意
给定一个 \(m \times m\) 的棋盘,棋盘上有 n 个格子是有颜色的,其他格子都是没有颜色的。
颜色分为两种,分别用 0 和 1 来表示。
一开始你在 \((1, 1)\),你想走到 \((m, m)\) 上。你只能上下左右走,不可走对角线。
对于任何时候,你所站的格子必须是有颜色的,包括走到 \((m, m)\) 时。
- 当你从一个格子走向另一个有色格子时,需判断。若颜色相同,可直接移动;否则需花费1金币。
- 当你从一个格子走向另一个无色格子时,需花费2金币,并使用魔法将目标格子变为两种颜色中的任意一种。
- 魔法不能连续使用
- 当你离开目标格子时,目标格子恢复无色。
问:最少需要多少金币?
你有魔法你为啥不直接飞过去呢?
思路
dfs
和 bfs
都可以,这里用 bfs
。
直接模拟
推状态
二维状态解决:(x, y, ma, lv, color)
表示走到 \((x,y)\),是否使用魔法(若是,则 \(ma\) 为 \(1\),否则为 \(0\)),花了 \(lv\) 个金币,当前格子颜色为 \(color\)
所以可以轻松推出初始状态 (1, 1, 0, 1, co[1][1])
此处我为了偷个懒,所以把他的金币数量设为了 \(1\),方便直接判断这个点有没有走过,最后输出答案时需要减去这个 \(1\)
推转移记录
由于转移“边权”并非简单的 \(0\) 和 \(1\),所以可以采用优先队列。
为了让答案最优,可以发现排序规则是花费金币数量少的优先。
推转移
我才不告诉你这里是按个人认为的思路难度从小到大排序的呢!
基本转移(坐标)
首先,我们知道它是可以上下左右转移的,那么状态里的前两项就可以很轻松的解决了。
设上下左右转移到的目标点坐标为 (nx, ny)
。
lv 金币
多看看题面描述,其实也不难。
- 如果不用魔法,那么直接判断这个点颜色是否和目标点一样,按题面描述模拟即可。
- 如果用了,那么就需要额外花费 \(2\) 金币,将目标点暂时变为某种颜色,然后也是直接模拟即可。
color 颜色
简单。
- 如果目标点是有色的,那么直接用
co[nx][ny]
就行。 - 否则,枚举一下是用魔法把目标格子暂时变为哪种颜色,因为你保不准哪种颜色更优。
- (📢注意:这里的前提是魔法能用!详见下面。)
ma 魔法
也很简单。
- 如果它所占的格子原本是无色的,那么肯定是不能再用魔法的。
- 若目标格子无色,那么这种转移也就不成立了。
- 否则,\(ma\) 就是 \(0\),这次转移也就成功了。
- 如果这个格子原本就有色,那么魔法随便用。这种情况应该不用再说了,题目讲的很清晰。
Code
总的来说还是挺简单的。
点击查看代码
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
const int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, -1, 0, 1}; // 方向数组,这个应该不用说了吧
int n, m, x, y, z;
int co[110][110], f[110][110];
struct node {
int x, y, co, lv, ma;
bool operator <(const node &i) const { // 简单的排序规则
return lv > i.lv;
}
};
priority_queue<node> pq; // 优先队列
void record (int x, int y, bool ma, int lv, int color) {
if (f[x][y]) { // 已经求解过
return ; // 直接goodbye
}
pq.push({x, y, color, lv, !ma}); // 压入状态
// 由于当前状态不一定是最优的,所以这里不能够直接统计
}
void bfs () {
record(1, 1, 0, 1, co[1][1]);
while (!pq.empty()) {
node t = pq.top();
pq.pop(); // 记得弹出队头啊!
if (f[t.x][t.y]) { // 已经求解过
continue;
}
f[t.x][t.y] = t.lv; // 要放在这里去统计答案
for (int i = 0; i < 4; i++) {
int nx = t.x + dx[i], ny = t.y + dy[i]; // 目标格子的坐标
if (nx >= 1 && nx <= n && ny >= 1 && ny <= n) { // 首先判断坐标是否合法
if (co[nx][ny]) { // 有色
record(nx, ny, 0, t.lv + (t.co != co[nx][ny]), co[nx][ny]); // 转移
} else if (t.ma) { // 没色
record(nx, ny, 1, t.lv + 2 + (t.co != 1), 1); // 枚举
record(nx, ny, 1, t.lv + 2 + (t.co != 2), 2);
}
}
}
}
}
int main(){
ios::sync_with_stdio(0), cin.tie(0);
cin >> n >> m;
for (int i = 1; i <= m; i++) {
cin >> x >> y >> z;
co[x][y] = z + 1; // 颜色
}
bfs();
cout << f[n][n] - 1; // 目标状态,记得减去一开始那个1,这里还能顺路把不能到达的情况直接输出-1
return 0;
}