洛谷P3956 [NOIP2017 普及组] 棋盘 题解+AC代码

题目

题目传送门

读题

1. 走同色的格子不花钱

    走异色的格子花 1 元

    走无色的格子花 2 元

2. 染色很短暂,且不连续

什么意思? 从染色的格子走出后颜色消失。 无法同时走两个格子

思路

很像 BFS / DFS 或者是DP。 我一眼洪水填充

DP

首先考虑DP。

那我们可以设一个状态:

f[i][j][0,1,2] 表示走到 i 行 j 列的格子。 

0 表示非无色格子(即格子为红色或黄色)

1 表示无色格子染成红色

2 表示无色格子染成黄色

接下来考虑状态转移方程

如果是0 随便走

如果是1 不能走无色格子,现在是红色

如果是2 不能走无色格子,现在是黄色

那么则有状态转移方程:

f(i,j,0)=\left\{\begin{matrix} f(i+1,j,0)+1 \\f(i-1,j,0)+1 \\f(i,j-1,0)+1 \\ f(i,j+1,0)+1 \end{matrix}\right.

但是这里有问题:

总所周知DP有个特性叫无后效性  Tips:无后效性:已经求解的子问题,不会再受到后续决策的影响

但是如果按照上述的状态转移方程

就会发现会出现后效性。

举例:比如往左走一格,又往右走一格(回去了),再往左走一格。这样就重复了。

所以我们就要使用记忆化搜索

记忆化搜索是一种通过记录已经遍历过的状态的信息,从而避免对同一状态重复遍历的搜索实现方式。

但是记忆化搜索 + DP 的代码太过复杂。 作者是蒟蒻写不出来其实是懒

因此我们考虑另外一种思路:

建图

建图,提取图论模型

我们将每一个格子视为一个点.。

我们考虑红黄点如何连边:

如果相邻,那么就连一条边权为0或1的边

如果相差两个以内:连边权为2或3的边

把图建出后,直接跑最短路即可。

写代码

枚举一个格子(i,j)

看相邻的格子(i+1,j) (i,j+1)(i-1,j)(i,j-1)

如果有颜色(黄色或红色)则直接连边权为0或1的边如果

如果无颜色 则再向外拓展一格,碰到有色格子连边权2或3的边

AC代码(保姆注释)

#include <cstring>
#include <iostream>

using namespace std;

const int maxM = 105;
const int maxN = 1005;
const int dx[] = {0, 0, 1, -1};
const int dy[] = {1, -1, 0, 0};

int m, n;
int color[maxM][maxM];
int id[maxM][maxM];
// color[i][j] 表示网格图中 i 行 j 列的格子的颜色
// id[i][j] 表示网格图中 i 行 j 列格子对应的最短路图的编号

struct Mesh
{ int x, y, c; };

Mesh a[maxN];

// 分模块地完成任务

namespace GRAPH
{
// 这里就只处理和图论有关的内容,包括初始化、建图、最短路
// 假设是一个 n 个点的图,边可以添加,要支持查询 1 号点到任意点的最短路。

int dis[maxN]; // 从 1 号点到 i 号点的最短距离
int G[maxN][maxN]; // n 个点的邻接矩阵

void init() // 初始化
{ memset(G, 0x3F, sizeof(G)); }

void link(int u, int v, int w) // 在 u 到 v 之间连长度为 w 的边
{ G[u][v] = G[v][u] = w; }

void BellmanFord(int S)
{
    memset(dis, 0x3F, sizeof(dis));
    dis[S] = 0;
    int flag = 0;
    // 最多松弛 n 轮,flag 表示这一轮是否松弛过
    for (int k = 1; k <= n; ++k)
    {
        // 枚举一条边 (i, j)
        for (int i = 1; i <= n; ++i)
            for (int j = 1; j <= n; ++j)
            {
                if (dis[i] > dis[j] + G[i][j])
                    dis[i] = dis[j] + G[i][j], flag = 1;
            }
        if (flag == 0)
            break;
        flag = 0;
    }
}

void print()
{
    for (int i = 1; i <= n; ++i)
        for (int j = i + 1; j <= n; ++j) if (G[i][j] < 10)
        {
            cout << i << ' ' << j << ' ' << G[i][j] << endl;
        }
}

}

int main()
{
    memset(color, -1, sizeof(color));
    cin >> m >> n;
    for (int i = 1; i <= n; ++i)
    {
        cin >> a[i].x >> a[i].y >> a[i].c;
        color[a[i].x][a[i].y] = a[i].c;
        id[a[i].x][a[i].y] = i;
    }
    GRAPH::init();

    // 枚举一个有色格子 (i, j),尝试从这个有色格子出发找它周围的一些格子连边
    for (int i = 1; i <= m; ++i)
    {
        for (int j = 1; j <= m; ++j) if (color[i][j] != -1)
        {
            for (int d = 0; d < 4; ++d)
            {
                int ii = i + dx[d];
                int jj = j + dy[d];
                if (ii < 1 or ii > m or jj < 1 or jj > m)
                    continue;
                // 用 u 表示 (i, j) 这一格是第几个点
                int u = id[i][j], v;
                if (color[ii][jj] != -1)
                {
                    v = id[ii][jj];
                    GRAPH::link(u, v, color[i][j] != color[ii][jj]);
                    continue;
                }
                // 现在,(ii, jj) 是无色格子
                for (int k = 0; k < 4; ++k)
                {
                    int iii = ii + dx[k];
                    int jjj = jj + dy[k];
                    if (iii < 1 or iii > m or jjj < 1 or jjj > m)
                        continue;
                    if (color[iii][jjj] == -1)
                        continue;
                    // 现在,(iii, jjj) 是与 (i, j) 相距不超过两格,中间有无色格子连着的有色格子
                    v = id[iii][jjj];
                    GRAPH::link(u, v, 2 + int(color[i][j] != color[iii][jjj])); 
                }
            }
        }
    }
    GRAPH::BellmanFord(id[1][1]);
    int ans = 1e9;
    if (color[m][m] != -1)
        ans = min(ans, GRAPH::dis[id[m][m]]);
    else
    {
        if (color[m][m-1] != -1)
            ans = min(ans, GRAPH::dis[id[m][m-1]] + 2);
        if (color[m-1][m] != -1)
            ans = min(ans, GRAPH::dis[id[m-1][m]] + 2);
    }
    if (ans < int(1e9))
        cout << ans << endl;
    else
        cout << -1 << endl;
    return 0;
}

  • 17
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值