洛谷P3956「NOIP2017普及组」《棋盘》
原创建时间:2018-10-02 22:23:11
三种搜索剪枝
题目描述
有一个\(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:
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:
5 5
1 1 0
1 2 0
2 2 1
3 3 1
5 5 0
输出样例
Case #1:
8
Case #2:
-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需要传递四个参数:
int
x坐标和y坐标int
当前使用的金币数量bool
当前是否使用了膜法
在四向DFS中,需要进行以下几点判断:
- 当前格是否有颜色
若无颜色且并未使用膜法,则使用膜法,使用金币数量+2,继续DFS;
若无颜色且使用过膜法,没救了 - 当前格颜色和下一格颜色是否相同
若颜色相同,直接进行下一步DFS;
若颜色不同,使用金币数量+1,继续DFS
要注意的是,\(f\)数组的赋值要在判断是否走到终点之前,最优性剪枝之后,不然可能出现赋值不上的情况
代码实现
/* -- 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;
}