You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

153 lines
6.6 KiB

2 years ago
## [$AcWing$ $471$. 棋盘](https://www.acwing.com/problem/content/description/473/)
### 一、题目描述
有一个 $m×m$ 的棋盘,棋盘上每一个格子可能是红色、黄色或没有任何颜色的。
你现在要从棋盘的最左上角走到棋盘的最右下角。 
任何一个时刻,你所站在的位置必须是有颜色的(不能是无色的),你只能向上、下、左、右四个方向前进。
当你从一个格子走向另一个格子时,如果两个格子的颜色相同,那你不需要花费金币;如果不同,则你需要花费 $1$ 个金币。 
另外,你可以花费 $2$ 个金币施展魔法让下一个无色格子暂时变为 你当前所处格子的颜色。
但这个魔法不能连续使用,而且这个魔法的持续时间很短,也就是说,如果你使用了这个魔法,走到了这个暂时有颜色的格子上,你就不能继续使用魔法;只有当你离开这个位置,走到一个本来就有颜色的格子上的时候,你才能继续使用这个魔法,而当你离开了这个位置(施展魔法使得变为有颜色的格子)时,这个格子恢复为无色。 
现在你要从棋盘的最左上角,走到棋盘的最右下角,求花费的最少金币是多少?
**输入格式**
数据的第一行包含两个正整数 $mn$,以一个空格分开,分别代表棋盘的大小,棋盘上有颜色的格子的数量。  
接下来的 $n$ 行,每行三个正整数 $xyc$,分别表示坐标为 $(xy)$ 的格子有颜色 $c$,其中 $c=1$ 代表黄色,$c=0$ 代表红色。
相邻两个数之间用一个空格隔开。棋盘左上角的坐标为 $(1,1)$,右下角的坐标为 $(m,m)$
。  
棋盘上其余的格子都是无色,保证棋盘的左上角,也就是 **$(1,1)$ 一定是有颜色的**。
**输出格式**
输出一行,一个整数,表示花费的金币的最小值,如果无法到达,输出 $1$。
**数据范围**
$1≤m≤100,1≤n≤1000$
**输入样例**
```cpp {.line-numbers}
5 7
1 1 0
1 2 0
2 2 1
3 3 1
3 4 0
4 4 1
5 5 0
```
**输出样例**
```cpp {.line-numbers}
8
```
### 二、题意抽象
走在一个棋盘上,棋盘上染着颜色,有三种颜色:红、黄、无,当你从一个格子走向另一个格子时,同色格子不花费,异色格子花费$1$,无色格子不能走,但是可以用魔法将其染成当前所处格子的颜色,花费$2$。求$(1,1)$到$(m,m)$的最短路。
### 三、解题思路
因为这个数据范围非常小,是$1≤m≤100$,代表我们棋盘上至多有$10000$个格子,所以我们就有了一个爆搜的思路::问题来了,如何`dfs`求解?
**状态表示**
我们首先分析一下状态转移的时候必须要考虑的因素:
- ① 首先当然是状态点的坐标$(x,y)$,这个不存就不能做到正确的转移,会到别的点
- ② 其次是花费,因为每个点到其他点的花费按照规则有一定的区别,所以也要存下花费
- ③ 有没有用过魔法,因为不能用连续的两次魔法,所以如果这个点是用过魔法的,那转移过去的时候就不能也用魔法
这样一通分析下来,我们$dfs$需要的参数差不多就出来了
$$\large dfs(x,y,cost,used)$$
**状态转移**
总感觉这写着像$DP$,但是又找不到别的词来形容,所以只能这样了$()$
首先我们继续进行分类:
对于每个由$(x,y)$转移过来的节点$(nx,ny)$,我们进行如下讨论:
- ① 当超出棋盘时,不能转移,做 `可行性剪枝`
- ② 当$(nx,ny)$无色时,再分两类:
- 如果在$(x,y)$点已经用过魔法,那么由于不能连续使用,不可转移
- 如果在$(x,y)$点没有用过魔法,那么可以使用魔法染色,花费$2$
- ③ 当$(nx,ny)$有色时,再分两类:
- $(nx,ny)$与$(x,y)$同色:转移,花费$0$
- $(nx,ny)$与$(x,y)$不同色:转移,花费$1$
总结起来就是
- ① 不可转移
-
- $dfs(x,y,cost,1)$ → 无法转移,用过魔法
- $dfs(x,y,cost,0)→dfs(nx,ny,cost+2,1)$
-
- $dfs(x,y,cost,used)→dfs(nx,ny,cost,0)$
- $dfs(x,y,cost,used)→dfs(nx,ny,cost+1,0)$
**剪枝**
这题不剪枝是过不了的,会爆栈。
所以我们可以加一个很简单的剪枝,当走到某个点的花费已经比目前最优解贵了,就不搜了,因为无论如何都不能更好,没有搜索的价值。
### 四、实现代码
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
const int N = 110, INF = 0x3f3f3f3f;
int n, m;
int g[N][N]; // 原始地图
int d[N][N]; // 记录最短距离,这个东西的作用是用来进行最优化剪枝,这个非常妙
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
void dfs(int x, int y, int cost, int used) {
if (d[x][y] <= cost) return; // 最优化剪枝,排除非最优路线
d[x][y] = cost; // 记录最优路线花费
if (x == m && y == m) return; // 递归出口
for (int i = 0; i < 4; i++) {
int nx = x + dx[i], ny = dy[i] + y;
if (nx < 1 || nx > m || ny < 1 || ny > m) continue;
if (g[nx][ny] == -1) { // 下一个位置是无色
if (used) continue; // 当前位置已使过魔法,下一个就无法继续使用魔法,无色不能变有色,无效
// 如果当前位置没有使用过魔法的话,下一个位置是可以使用魔法的,那么变成什么颜色呢?
// 你可以花费 2 个金币施展魔法让下一个无色格子暂时变为 你当前所处格子的颜色
g[nx][ny] = g[x][y];
dfs(nx, ny, cost + 2, 1); // 走入这个(nx,ny),标识费用+2同时标注(nx,ny)的魔法是已使用状态,继续传递
g[nx][ny] = -1; // 也可以不走这个(nx,ny),需要回溯
} else if (g[nx][ny] == g[x][y]) // 下一个位置颜色相同
dfs(nx, ny, cost, 0); // 直接走过去,费用不变,也没有使用魔法
else
dfs(nx, ny, cost + 1, 0); // 有色到有色颜色不同花费1个金币没有使用魔法
}
}
int main() {
// 加快读入
ios::sync_with_stdio(false), cin.tie(0);
cin >> m >> n;
memset(g, -1, sizeof g); // 标识无色是-1
while (n--) {
int a, b, c;
cin >> a >> b >> c;
g[a][b] = c; // 有颜色你就说是啥颜色吧
}
memset(d, 0x3f, sizeof d);
dfs(1, 1, 0, 0);
if (d[m][m] == INF)
puts("-1");
else
cout << d[m][m] << endl;
return 0;
}
```