洛谷P3956「NOIP2017普及组」《棋盘》

三种搜索剪枝

题目链接

题目描述

有一个 m \times m 的棋盘,棋盘上每一个格子可能是红色、黄色或没有任何颜色的。你现在要从棋盘的最左上角走到棋盘的最右下角。

任何一个时刻,你所站在的位置必须是有颜色的(不能是无色的), 你只能向上、 下、左、 右四个方向前进。当你从一个格子走向另一个格子时,如果两个格子的颜色相同,那你不需要花费金币;如果不同,则你需要花费 1个金币。

另外, 你可以花费 2 个金币施展魔法让下一个无色格子暂时变为你指定的颜色。但这个魔法不能连续使用, 而且这个魔法的持续时间很短,也就是说,如果你使用了这个魔法,走到了这个暂时有颜色的格子上,你就不能继续使用魔法; 只有当你离开这个位置,走到一个本来就有颜色的格子上的时候,你才能继续使用这个魔法,而当你离开了这个位置(施展魔法使得变为有颜色的格子)时,这个格子恢复为无色。

现在你要从棋盘的最左上角,走到棋盘的最右下角,求花费的最少金币是多少?

Input / Output 格式 & 样例

输入格式

第一行包含两个正整数 m, n ,以一个空格分开,分别代表棋盘的大小,棋盘上有颜色的格子的数量。

接下来的 n 行,每行三个正整数 x, y, c , 分别表示坐标为 (x,y) 的格子有颜色 c

其中 c=1 代表黄色, c=0 代表红色。 相邻两个数之间用一个空格隔开。 棋盘左上角的坐标为 (1, 1) ,右下角的坐标为 ( m, m)

棋盘上其余的格子都是无色。保证棋盘的左上角,也就是 (1,1) 一定是有颜色的。

输出格式

一个整数,表示花费的金币的最小值,如果无法到达,输出-1。

输入样例

Case #1:

1
2
3
4
5
6
7
8
5 7
1 1 0
1 2 0
2 2 1
3 3 1
3 4 0
4 4 1
5 5 0

Case #2:

1
2
3
4
5
6
5 5
1 1 0
1 2 0
2 2 1
3 3 1
5 5 0

输出样例

Case #1:

1
8

Case #2:

1
-1

样例解释 & 其他说明

对于 30\% 的数据, 1 ≤ m ≤ 5, 1 ≤ n ≤ 10

对于 60\% 数据, 1 ≤ m ≤ 20, 1 ≤ n ≤ 200

对于 100\% 的数据, 1 ≤ m ≤ 100, 1 ≤ n ≤ 1,000

解析

我们并不需要维护某一个点是否走过

我们需要判断边界、白格子、最优性剪枝和走到终点四种情况

mp 数组存图,规定0表示白色,1表示红色,2表示黄色

f_{i,j} 表示 1,1 i,j 的最少花费

本题主要的难点在于加入了膜法机制

那么DFS需要传递四个参数:

  • intx坐标和y坐标
  • int当前使用的金币数量
  • bool当前是否使用了膜法

在四向DFS中,需要进行以下几点判断:

  • 当前格是否有颜色
    若无颜色且并未使用膜法,则使用膜法,使用金币数量+2,继续DFS;
    若无颜色且使用过膜法,没救了
  • 当前格颜色和下一格颜色是否相同
    若颜色相同,直接进行下一步DFS;
    若颜色不同,使用金币数量+1,继续DFS

要注意的是, f 数组的赋值要在判断是否走到终点之前,最优性剪枝之后,不然可能出现赋值不上的情况

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
/* -- Basic Headers -- */
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cctype>
#include <algorithm>

/* -- STL Iterator -- */
#include <vector>
#include <string>
#include <stack>
#include <queue>

/* -- Defined Functions -- */
#define For(a,x,y) for (int a = x; a <= y; ++a)
#define Bak(a,y,x) for (int a = y; a >= x; --a)
using namespace std;

namespace FastIO {
void DEBUG(char comment[], int x) {
cerr << comment << x << endl;
}

inline int getint() {
int s = 0, x = 1;
char ch = getchar();
while (!isdigit(ch)) {
if (ch == '-') x = -1;
ch = getchar();
}
while (isdigit(ch)) {
s = s * 10 + ch - '0';
ch = getchar();
}
return s * x;
}
inline void __basic_putint(int x) {
if (x < 0) {
x = -x;
putchar('-');
}
if (x >= 10) __basic_putint(x / 10);
putchar(x % 10 + '0');
}

inline void putint(int x, char external) {
__basic_putint(x);
putchar(external);
}
}

namespace Solution {
const int MAXM = 100 + 10;

int f[MAXM][MAXM];
int mp[MAXM][MAXM];
#define WHITE 0
#define RED 1
#define YELLOW 2

const int dx[5] = {0, 0, 0, -1, 1};
const int dy[5] = {0, -1, 1, 0, 0};

int m, n, ans = 2147482333;

void DaFaShi(int x, int y, int nowSum, bool usedMogic) {
// 苟利国家生死以
// 岂因祸福避趋之
// 你们啊,不要总是想弄个大新闻
// 说什么使用膜法
// 再把我批判一番
if (x < 1 || y < 1 || x > m || y > m) return; // 边界
if (mp[x][y] == WHITE) return; // 走到白格子
if (nowSum >= f[x][y]) return; // 最优性剪枝
f[x][y] = nowSum;
if (x == m && y == m) {
ans = std::min(nowSum, ans);
return;
// 搜索完成
}

For (i, 1, 4) {
int nx = x + dx[i];
int ny = y + dy[i];
if (mp[nx][ny] != WHITE) {
// 有颜色
if (mp[nx][ny] == mp[x][y]) DaFaShi(nx, ny, nowSum, false);
// 颜色相同,继续往后搜
else DaFaShi(nx, ny, nowSum + 1, false); // 颜色不同,花费金币
} else if (mp[nx][ny] == WHITE && !usedMogic){
// 没颜色且没用膜法
mp[nx][ny] = mp[x][y]; // 念诗,使用膜法
DaFaShi(nx, ny, nowSum + 2, true); // 使用膜法花费2金币
mp[nx][ny] = WHITE; // 回溯
}
}
}
}

int main(int argc, char *const argv[]) {
#ifdef HANDWER_FILE
freopen("testdata.in", "r", stdin);
freopen("testdata.out", "w", stdout);
#endif
using namespace Solution;
using namespace FastIO;
memset(f, 0x7f, sizeof(f));
m = getint();
n = getint();
For (i, 1, n) {
int x, y, c;
x = getint();
y = getint();
c = getint();
mp[x][y] = c + 1;
}
DaFaShi(1, 1, 0, false);
if (ans == 2147482333) puts("-1");
else putint(ans, '\n');
return 0;
}