【NOIP普及组2017】棋盘Chess

魔法四溢的《棋盘》


题目描述

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

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

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

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

输入输出格式

输入格式:
数据的第一行包含两个正整数 m, n,以一个空格分开,分别代表棋盘的大小,棋盘上有颜色的格子的数量。
接下来的 n 行,每行三个正整数 x, y, c, 分别表示坐标为( x, y)的格子有颜色 c。
其中 c=1 代表黄色, c=0 代表红色。 相邻两个数之间用一个空格隔开。 棋盘左上角的坐标为( 1, 1),右下角的坐标为( m, m)。
棋盘上其余的格子都是无色。保证棋盘的左上角,也就是( 1, 1) 一定是有颜色的。

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

输入输出样例

输入样例#1:
5 7
1 1 0
1 2 0
2 2 1
3 3 1
3 4 0
4 4 1
5 5 0
输出样例#1:
8

输入样例#2:
5 5
1 1 0
1 2 0
2 2 1
3 3 1
5 5 0
输出样例#2:
-1

数据规模与约定

对于 30%的数据, 1 ≤ m ≤ 5, 1 ≤ n ≤ 10。
对于 60%的数据, 1 ≤ m ≤ 20, 1 ≤ n ≤ 200。
对于 100%的数据, 1 ≤ m ≤ 100, 1 ≤ n ≤ 1,000。

分析-魔法后遗症

看到“魔法”两个字,立即勾起了我对NOIP普及组不好的回忆……
遥想去年的2016年压轴题魔法阵……

咳咳,尽管如此,我还是好好读了一遍题,开始思考这个魔法的数学性质。是不是有些时候一定要用魔法变红色才最优?是不是魔法根本没用?实际是有些规律的,结果当时脑子短路,找了半天找不出来,在0.3秒的抉择下,我决定,
爆搜吧

搜索-高端的剪枝

“代码写得好,不是正解也能跑;
代码写不好,即使正解也得爆。”——OI界的真理
嗯经过后面一句无数次的洗礼,终于有机会享受前一句话的福利了。
做题的时候我就在想:“不行!即使爆搜,也要华丽地爆搜!”于是就想方设法地剪枝。
剪枝 * 0:拒绝可能爆栈的深搜,我们使用广搜
剪枝 * 1:使用优先队列,使代价较少的先被处理
剪枝 * 2:记录预估的最小值,如果当前状态大于预估最值,就剪掉
【因为要稳一点,魔法颜色就选了2种转移】
代码:

#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int MAXN = 100;
const int dir[4][2]={{0,1},{1,0},{0,-1},{-1,0}};
int a[MAXN+5][MAXN+5];
int MIN[MAXN+5][MAXN+5][2];
struct NODE{
    int x,y,t,c;
}p;
bool operator<(NODE a,NODE b)
{
    return a.t>b.t;
}
priority_queue<NODE>que;
int main()
{
    //freopen("chess.in","r",stdin);
    //freopen("chess.out","w",stdout);
    memset(a,-1,sizeof(a));
    memset(MIN,63,sizeof(MIN));
    int m,n;
    scanf("%d %d",&m,&n);
    for(int i=1;i<=n;i++)
    {
        int x,y,c;
        scanf("%d %d %d",&x,&y,&c);
        a[x][y] = c;
    }
    p.x = 1;p.y = 1;
    p.t = 0;p.c = a[1][1];
    MIN[1][1][a[1][1]] = 0;
    que.push(p);
    do
    {
        p = que.top();que.pop();
        if( p.x == m && p.y == m )
        {
            printf("%d\n",p.t);
            return 0;
        }
        if( p.t < MIN[p.x][p.y][p.c] ) continue;
        for(int i=0;i<4;i++)
        {
            NODE q;
            q.x = p.x + dir[i][0];
            q.y = p.y + dir[i][1];
            if( q.x < 1 || q.y < 1 || q.x > m || q.y > m ) continue;
            if( a[q.x][q.y] == -1 )
            {
                if( a[p.x][p.y] == -1 ) continue;
                q.t = p.t + 2;
                q.c = p.c;
                if( q.t < MIN[q.x][q.y][q.c] )
                {
                    MIN[q.x][q.y][q.c] = q.t;
                    que.push(q);
                }
                q.c = !(p.c);
                q.t++;
                if( q.t < MIN[q.x][q.y][q.c] )
                {
                    MIN[q.x][q.y][q.c] = q.t;
                    que.push(q);
                }
            }
            else
            {
                q.t = p.t;q.c = a[q.x][q.y];
                if( q.c != p.c ) q.t++;
                if( q.t < MIN[q.x][q.y][q.c] )
                {
                    MIN[q.x][q.y][q.c] = q.t;
                    que.push(q);
                }
            }
        }
    }while(!que.empty());
    printf("-1\n");
    return 0;
}

然后考试当天,我疯狂拿极端数据测试,结果这个代码都活下来了
而且效率还不低,感受到了来自“搜索”无尽的嘲讽
好吧我也不知道为什么莫名其妙就AC了

正解-图论大佬专场

好的我们进入正题
首先我们要把魔法这种不定因素定下来。我们讨论几种不同的需要使用魔法的情况:
红 空 红红 空 黄黄 空 黄黄 空 红
通过找规律,我们发现,空两边如果同色,转移代价为使用魔法的代价。空两边如果异色,转移代价为使用魔法的代价+异色转移的代价。我们进一步发现:一定是这样的。
于是我就可以直接看作从空的一边转移到空的另一边,魔法因素就去掉了但是这意味着棋盘的形状也就变了,
它变成了一张图
相邻的有色格子连边,同色边权为0,异色边权为1
另外就是空的格子,即上面讨论的这些。
我们的目标就是从一个格子到另一个格子的最小代价,于是愉快地Dijkstra吧。
代码我就不给了,上面的代码已经够用了吧

最后-一些疑惑?

有一点问题仍未解决,为什么搜索能过?尽管我是自己没事找事,但是还是很疑惑
我们再把我们用的剪枝贴出来看看:

剪枝 * 0:拒绝可能爆栈的深搜,我们使用广搜
剪枝 * 1:使用优先队列,使代价较少的先被处理
剪枝 * 2:记录预估的最小值,如果当前状态大于预估最值,就剪掉

如果我们第二个剪枝描述改改,就变成了:记录预估的最短路,如果当前这条边可以对它进行松弛,就更改最短路,否则不理会
诶?是不是和最短路算法有着异曲同工之妙?
再看看第一个剪枝,诶~这不就是Dijkstra的操作吗?
也就是说,上面的这个搜索其实就是戴上帽子,蒙上口罩的最短路算法。只不过本质其实是一样的,遮拦也遮拦不住。
也就是说我一不小心就打出来一个最短路算法……【被打】

END

就是这样,新的一天里,也请多多关照哦(ノω<。)ノ))☆.。~

猜你喜欢

转载自blog.csdn.net/tiw_air_op1721/article/details/78700537