洛谷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需要传递四个参数:

  • intx坐标和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;
}

posted @ 2019-10-26 20:46  Handwer  阅读(281)  评论(0编辑  收藏  举报