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.

6.6 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

AcWing 471. 棋盘

一、题目描述

有一个 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

输入样例

5 7
1 1 0
1 2 0
2 2 1
3 3 1
3 4 0
4 4 1
5 5 0

输出样例

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)

剪枝 这题不剪枝是过不了的,会爆栈。 所以我们可以加一个很简单的剪枝,当走到某个点的花费已经比目前最优解贵了,就不搜了,因为无论如何都不能更好,没有搜索的价值。

四、实现代码

#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;
}