◆竞赛题目◆◇NOIP 2017 普及组◇ Chess 棋盘
2017-11-20 14:07
295 查看
◇NOIP 2017 普及组◇ Chess
Description有一个m × m的棋盘,棋盘上每一个格子可能是红色、黄色或没有任何颜色的。你现在
要从棋盘的最左上角走到棋盘的最右下角。
任何一个时刻,你所站在的位置必须是有颜色的(不能是无色的),你只能向上、下、
左、右四个方向前进。当你从一个格子走向另一个格子时,如果两个格子的颜色相同,那你不需要花费金币;如果不同,则你需要花费1 个金币。
另外,你可以花费2 个金币施展魔法让下一个无色格子暂时变为你指定的颜色。但这个
魔法不能连续使用,而且这个魔法的持续时间很短,也就是说,如果你使用了这个魔法,走到了这个暂时有颜色的格子上,你就不能继续使用魔法;只有当你离开这个位置,走到一个本来就有颜色的格子上的时候,你才能继续使用这个魔法,而当你离开了这个位置(施展魔法使得变为有颜色的格子)时,这个格子恢复为无色。
现在你要从棋盘的最左上角,走到棋盘的最右下角,求花费的最少金币是多少?
Input
第一行包含两个正整数m,n,以一个空格分开,分别代表棋盘的大小,棋盘上
有颜色的格子的数量。
接下来的 n 行,每行三个正整数x,y,c,分别表示坐标为(x,y)的格子有颜色c。
其中c=1 代表黄色,c=0 代表红色。相邻两个数之间用一个空格隔开。棋盘左上角的坐标为(1, 1),右下角的坐标为(m, m)。
棋盘上其余的格子都是无色。保证棋盘的左上角,也就是(1,1)一定是有颜色的。
Output
输出一行,一个整数,表示花费的金币的最小值,如果无法到达,输出-1。
Sample Input
5 7 1 1 0 1 2 0 2 2 1 3 3 1 3 4 0 4 4 1 5 5 0
Sample Output
8
题目解析
这次NOIP一如既往,第三题还是考察搜索,唯一不一样的就是直接搜索似乎会超时……据一位大牛说,这道题本来考察的是广度优先搜索再加上一个优先队列优化,然而 —— 作者用了深度优先搜索,加上剪枝优化。
一、肯定先说作者自己的代码啊
这其实是一个迷宫问题,非要把它放在棋盘上……因此我们很容易想到用搜索解决迷宫问题。不知道为什么,我几乎想都没想就选择用深度优先搜索,而且凭借经验加上了一个判重。这个深度优先搜索没有什么特别的 —— 参数包含坐标(x,y),以及到达当前位置所花费的金币数量sum 。除此之外也就是考虑到不能连续使用魔法,还加上了一个bool变量use,表示走到当前位置是否用了魔法。那么从简单的开始,不考虑使用魔法。常规的,我们用 F[4][2]={{0,1},{1,0},{0,−1},{−1,0}} 数组保存方向(上右下左),然后用int的二维数组 chess 保存棋盘平面,在输入时同时处理出chess。起点在棋盘左上角,也就是 (1,1) ,因此搜索函数 search() 的起始坐标为 (1,1) 。对于每一个位置,枚举它的4个方向,若下一个位置是有颜色的格子,就根据题目所说增加 sum ,否则就舍去该方向。但是这样难免会走重复的路,因此较常规的做法是用一个二维的bool数组 Flag[i][j] 表示 (i,j) 是否走到过,但是需要回溯,这样就难免会慢一些,这里就做出剪枝的优化——用 int 类型的二维数组 Flag[i][j] 表示当前已经发现的到达 (i,j) 的最小花费的金币数量。那么我们就需要初始化 Flag 为最大值,即:
memset(Flag,0,sizeof Flag);
如果当前的 sum 比 Flag[x][y] 大(或者等于),则执行剪枝,否则更新 Flag[x][y] 为 sum。
接下来就是考虑使用魔法的情况 —— 这种情况只在下一步是空白格子时会产生。因此判断下一步的格子是否为空时,若是空白,则判断 use ,即上一步是否使用了魔法,如果 use=false 即上一步没有使用魔法,则改变下一步要走到的格子的颜色,进一步搜索,搜索完毕后回溯。
许多同学问我:“题目说这样改变格子的颜色只是一瞬间,走过了就恢复为空白吗?为什么可以在整个搜索完毕后再回溯?”其原因是搜索中进行了剪枝,如果改变这个格子颜色再后走到这一格子,则 sum≥Flag[x][y] 在剪枝优化中就会自动舍去。
还有一个问题——“有红黄两种颜色,那么把下一步的格子改变为什么颜色最优呢?”答案是当前脚下的格子的颜色。由于我们改变格子颜色是为了能通过这一格子到达下一个格子,这里就分2种情况——
①当前格子和通过改变了颜色的格子所到达的下一个格子的颜色相同,即:
那么非常明显需要把中间的空白格子改为现在脚下的格子的颜色。
②当前格子和通过改变了颜色的格子所到达的下一个格子的颜色不相同,即:
如果改为红色,则从红色格子到黄色格子需要1金币;如果改为黄色,则从黄色格子到红色格子同样需要1金币……也就是说,这2种是一样的,不过考虑到统一性,也采用改为当前脚下的格子的颜色的方案。
二、然后说说某一位大牛的代码吧
总体的思路是相同的,只是在实现上采用了完全不同的方法。应对数据量庞大的迷宫问题往往要采用广度优先搜索,但是这道题还需要优先队列优化——用优先队列代替原来的FIFO队列。框架与广度优先搜索完全一样,不一样的是代码中的 MIN 数组是3维的(也就是作者的 Flag 数组),完全没有必要——最后一维是格子的颜色,是为了避免改变空白格子颜色时的不同,但是之前作者提到过颜色不影响,也就没有必要了。
优先队列的类型是一个结构体 Node ,包含坐标 x、y,以及当前花费的金币数 t 、当前脚下的颜色 c 。是按金币数量 t 排序的小根堆。当到达左下角 (m,m) 时,输出并结束程序。在结构体里增加 c 有一个好处——由于它不会真正改变棋盘的颜色,可以实现描述中的“走过改变颜色的格子后格子恢复为空白”。而判断上一步是否使用过魔法,则是上一步的位置是否为 -1(即空)。同样更新 MIN 并且剪枝。不同的是代码中对2种颜色都尝试了改变。
我感觉没有错的程序(应该不会 TLE 吧)
//Problem 3 #include<cstdio> #include<cstring> #include<algorithm> using namespace std; int n,Num_Color,chess[105][105]; int Flag[105][105],F[4][2]={{0,1},{1,0},{0,-1},{-1,0}}; void Search(int x,int y,int sum,bool use) { if(sum>=Flag[x][y]) return; Flag[x][y]=sum; for(int i=0;i<4;i++) { int sx=x+F[i][0],sy=y+F[i][1]; if(sx<1 || sy<1 || sx>n || sy>n) continue; if(chess[sx][sy]==-1) { if(!use) chess[sx][sy]=chess[x][y],Search(sx,sy,sum+2,true),chess[sx][sy]=-1; continue; } if(chess[sx][sy]==chess[x][y]) Search(sx,sy,sum,false); else Search(sx,sy,sum+1,false); } } int main() { freopen("chess.in","r",stdin); freopen("chess.out","w",stdout); memset(chess,-1,sizeof chess); scanf("%d%d",&n,&Num_Color); for(int i=0,x,y,color;i<Num_Color;i++) scanf("%d%d%d",&x,&y,&color),chess[x][y]=color; memset(Flag,0x3f,sizeof Flag); const int Error=Flag[0][0]; Search(1,1,0,0); if(Error!=Flag ) printf("%d\n",Flag ); else printf("-1\n"); return 0; }
下面是某一位大牛的代码,是用的 广度优先搜索+优先队列 优化:
#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; }
The End
Thanks for reading!
-Lucky_Glass相关文章推荐
- 【NOIP普及组2017】棋盘Chess
- ◆竞赛题目◆◇NOIP 2017 普及组◇ 图书管理员
- NOIP 2017 普及组 棋盘 chess
- [noip 2017]普及组 T3 棋盘
- ◆竞赛题目◆◇NOIP2013普及组◇ 小朋友的数字
- NOIP 2017 普及组 棋盘
- ◆竞赛题目◆◇NOIP2016普及组◇ 魔法阵
- [NOIP2017普及组]棋盘
- [NOIP2017普及组]——棋盘
- 竞赛题目讲解-【NOIP2000复赛 普及组】单词接龙
- 2017NOIp 普及组第三题 棋盘
- 【NOIP2017普及组】棋盘
- ◆竞赛题目◆◇NOIP2015普及组◇求和
- NOIP2017棋盘(普及T3)
- ◆竞赛题目◆◇NOIP 2017◇ jump 跳房子
- NOIP2017普及组复赛题解
- 竞赛题目-【NOIP2012】马拦过河卒
- NOIP 2010 - 普及组 导弹拦截 贪心 重庆一中高2018级竞赛班第三次测试 2016.7.24 Problem 3
- 【用膝盖写代码系列】(3):NOIP2012普及组题目详解
- NOIP2017普及组复赛 T1