P3956 棋盘 题解

棋盘

题意

给定一个 \(m \times m\) 的棋盘,棋盘上有 n 个格子是有颜色的,其他格子都是没有颜色的。

颜色分为两种,分别用 0 和 1 来表示。

一开始你在 \((1, 1)\),你想走到 \((m, m)\) 上。你只能上下左右走,不可走对角线。

对于任何时候,你所站的格子必须是有颜色的,包括走到 \((m, m)\) 时。

  • 当你从一个格子走向另一个有色格子时,需判断。若颜色相同,可直接移动;否则需花费1金币。
  • 当你从一个格子走向另一个无色格子时,需花费2金币,并使用魔法将目标格子变为两种颜色中的任意一种。
    • 魔法不能连续使用
    • 当你离开目标格子时,目标格子恢复无色

问:最少需要多少金币?

你有魔法你为啥不直接飞过去呢?

思路

dfsbfs 都可以,这里用 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;
}

就这样轻松地A了!

posted @ 2023-02-22 22:30  wnsyou  阅读(47)  评论(0编辑  收藏  举报