hduacm 5433 Xiao Ming climbing题解
2015-09-16 22:55
218 查看
题目连接
http://acm.hdu.edu.cn/showproblem.php?pid=5433
这是一道BFS题目,不算简单也不算难,但对博主这种新手还是能学到很多东西的,题目大家可以简单看一下,英文不懂的同学可以去BC房间里查,BC非常贴心的给大家贴出了中文翻译。
第一眼看这个题目,博主当时以为这是一道带权值的无向图,但是,仔细审题发现,权重的计算需要设计到变量,也就是斗志,也就是剩余步数+1,所以这不是简单的带权图。
分析所有条件,已知的量是一共的步数,起点,终点,地图。
分析题目细节,当斗志为0的时候必死无疑,即便到了终点,而题目中斗志的范围是>=0,也就是可以为0
分析特殊情况,当起点和终点重合的时候,如果斗志为0,依然No Answer
通过对题目大致了解,可以断定这是一道图算法题,而且肯定需要使用搜索算法,我们选择的是带优先队列的广搜。
让我们先从思想上解决这道题目,首先思考,我们如何保证,从起点到达任意一个点的时候,我们的体力消耗最小?
—————–答案是:我们每一次都对体力消耗最小的情况进行处理
证明如下:(我们暂时假定斗志为10)
s为起点,e为终点,高度都是1(请不要嘲笑本人的绘图工具与水平),其余都标在这个上面
![](https://img-blog.csdn.net/20150916212141102)
我们首先操作s点,发现在其身边的规范点有两个,分别的(1,4)的3和(1,1)的2,为了迎合本人高中数学的习惯,以及数组把1当起点而不是0的习惯,我们将左下方作为起点,坐标从1计数,也就是说s点为(1,3),e点为(4,3),首先我们操作s点,发现两个标准点,然后将这选择两个点行走,我们发现,体力消耗(以下称之为cost)在这里发生了改变,分别为0.1与0.2,所以我们先操作cost最小情况,选择在(1,2)处继续行走,可以看到,接下来一路顺风,我们可以一直走到(3,1)处,接下来行走到向上向右行走都一样,cost都会大于(1,3)处的0.2了,然后我们将(3,2)与(4,1)放进行动队列中并,回到(1,3)处继续走动,最终得到的路线是s->1->3->4->2->2->e这一条。
从这个例子终我们发现,只要每一次都操作体力消耗最小的情况,那么得到的第一次得到的路径就是cost最小的,因为,在所有的可到达path中,cost最小的path肯定在某一情况下,在别的path还没有到达的情况下,cost就已经是最小从而不得不去操作他,比如cost最小是8,别的path都是10,但是在别的path在接触到终点前都维持着0消耗,而最短path在走出第一步就cost8了,那么在别的path都离终点一步之遥的时候,我们就操作最短path直接到达终点。
结论
BFS中,我们将点压进队列,如果保证每一次都对最短消耗的情况操作,那么每一个第一次到达的点消耗的体力都是最小的。
数据结构分析
要保证以体力消耗为基准,每一次处理的都是体力消耗最小的情况,那我们该使用什么样的数据结构对其数据进行操作,排序是肯定的,qsort或许是一个解决方案,但有没有更好的?
————答案是:heap,堆排序。
所谓堆,即是保证自上而下,上层节点一定大于或小于下呈所有节点的值的东西,是一种树形的递归结构,而这个结构能够保证堆顶的值一定是最大值或最小值,而加入新值以后的重建操作也不需要涉及所有的节点,只与堆的深度有关,合理的话时间复杂度仅为log(n),而且,stl为我们很好的实现了这一切,queue文件中有一个叫做priority_queue的数据结构,他是一个基于堆的结构,通过自定义的比较函数能够轻松实现最大堆与最小堆,并且为BFS每次提供队列中cost最小的情况,详情请大家百度。
最后的构思:
每个情况中除了x和y,还包括剩余的步数,所以我们应该使用一个三维向量vis[x][y][d]来表示我们对某个点是否进行过操作,这很重要,因为除了目标点外,其余的点我们都可能会走过很多次,不同的路径可能会有交错,相同的点可能在剩余不同步数时被走过,因为博主太水,所以有一个博主认为是但不太确定的推论,那就是,在我们使用优先队列的情况下,对于每一个确定的x,y,d,第一次被操作也就是vis[x][y][d]第一次变成true的那次,消耗的体力也是最小的,对,应该是的,应该是的
上代码
http://acm.hdu.edu.cn/showproblem.php?pid=5433
这是一道BFS题目,不算简单也不算难,但对博主这种新手还是能学到很多东西的,题目大家可以简单看一下,英文不懂的同学可以去BC房间里查,BC非常贴心的给大家贴出了中文翻译。
第一眼看这个题目,博主当时以为这是一道带权值的无向图,但是,仔细审题发现,权重的计算需要设计到变量,也就是斗志,也就是剩余步数+1,所以这不是简单的带权图。
分析所有条件,已知的量是一共的步数,起点,终点,地图。
分析题目细节,当斗志为0的时候必死无疑,即便到了终点,而题目中斗志的范围是>=0,也就是可以为0
分析特殊情况,当起点和终点重合的时候,如果斗志为0,依然No Answer
通过对题目大致了解,可以断定这是一道图算法题,而且肯定需要使用搜索算法,我们选择的是带优先队列的广搜。
让我们先从思想上解决这道题目,首先思考,我们如何保证,从起点到达任意一个点的时候,我们的体力消耗最小?
—————–答案是:我们每一次都对体力消耗最小的情况进行处理
证明如下:(我们暂时假定斗志为10)
s为起点,e为终点,高度都是1(请不要嘲笑本人的绘图工具与水平),其余都标在这个上面
我们首先操作s点,发现在其身边的规范点有两个,分别的(1,4)的3和(1,1)的2,为了迎合本人高中数学的习惯,以及数组把1当起点而不是0的习惯,我们将左下方作为起点,坐标从1计数,也就是说s点为(1,3),e点为(4,3),首先我们操作s点,发现两个标准点,然后将这选择两个点行走,我们发现,体力消耗(以下称之为cost)在这里发生了改变,分别为0.1与0.2,所以我们先操作cost最小情况,选择在(1,2)处继续行走,可以看到,接下来一路顺风,我们可以一直走到(3,1)处,接下来行走到向上向右行走都一样,cost都会大于(1,3)处的0.2了,然后我们将(3,2)与(4,1)放进行动队列中并,回到(1,3)处继续走动,最终得到的路线是s->1->3->4->2->2->e这一条。
从这个例子终我们发现,只要每一次都操作体力消耗最小的情况,那么得到的第一次得到的路径就是cost最小的,因为,在所有的可到达path中,cost最小的path肯定在某一情况下,在别的path还没有到达的情况下,cost就已经是最小从而不得不去操作他,比如cost最小是8,别的path都是10,但是在别的path在接触到终点前都维持着0消耗,而最短path在走出第一步就cost8了,那么在别的path都离终点一步之遥的时候,我们就操作最短path直接到达终点。
结论
BFS中,我们将点压进队列,如果保证每一次都对最短消耗的情况操作,那么每一个第一次到达的点消耗的体力都是最小的。
数据结构分析
要保证以体力消耗为基准,每一次处理的都是体力消耗最小的情况,那我们该使用什么样的数据结构对其数据进行操作,排序是肯定的,qsort或许是一个解决方案,但有没有更好的?
————答案是:heap,堆排序。
所谓堆,即是保证自上而下,上层节点一定大于或小于下呈所有节点的值的东西,是一种树形的递归结构,而这个结构能够保证堆顶的值一定是最大值或最小值,而加入新值以后的重建操作也不需要涉及所有的节点,只与堆的深度有关,合理的话时间复杂度仅为log(n),而且,stl为我们很好的实现了这一切,queue文件中有一个叫做priority_queue的数据结构,他是一个基于堆的结构,通过自定义的比较函数能够轻松实现最大堆与最小堆,并且为BFS每次提供队列中cost最小的情况,详情请大家百度。
最后的构思:
每个情况中除了x和y,还包括剩余的步数,所以我们应该使用一个三维向量vis[x][y][d]来表示我们对某个点是否进行过操作,这很重要,因为除了目标点外,其余的点我们都可能会走过很多次,不同的路径可能会有交错,相同的点可能在剩余不同步数时被走过,因为博主太水,所以有一个博主认为是但不太确定的推论,那就是,在我们使用优先队列的情况下,对于每一个确定的x,y,d,第一次被操作也就是vis[x][y][d]第一次变成true的那次,消耗的体力也是最小的,对,应该是的,应该是的
上代码
#include <iostream> #include <string.h> #include <queue> #include <algorithm> using namespace std; struct Data { int x, y; int d; double cost; Data(int x_, int y_, int d_, double cost_) :x(x_), y(y_), d(d_), cost(cost_){} }; struct DataCmp { bool operator()(const Data& a, const Data& b) { return a.cost > b.cost; } }; priority_queue<Data, vector<Data>, DataCmp> Q; char map[55][55]; bool vis[55][55][55]; //double cost[55][55][55]; int dx[4] = { 1, 0, -1, 0 }; int dy[4] = { 0, 1, 0, -1 }; int n, m, z; int sx, sy, ex, ey; inline bool out(int x, int y) { return x<1 || y<1 || x>n || y>m; } void input() { scanf("%d%d%d", &n, &m, &z); for (int i = 1; i <= n; ++i) { scanf("%s", map[i] + 1); } scanf("%d%d%d%d", &sx, &sy, &ex, &ey); } void init() { memset(vis, 0, sizeof(vis)); /*for (int i = 1; i <= n;++i) for (int k = 1; k <= m;++k) for (int p = 0; p <= z;++p) { 如果推论正确,那么使用优先队列的BFS时候 对cost进行存取就毫无意义 cost[i][k][p] = 1e80; }*/ while (!Q.empty()) Q.pop(); } void inputNeibourNode(const Data& D) { int x, y, d ; double cost; for (int i = 0; i < 4;++i) { x = D.x + dx[i]; y = D.y + dy[i]; d = D.d - 1; if (out(x,y)||map[x][y]=='#'||d<=0) continue; if (vis[x][y][d]==true) continue; cost = D.cost + 1.0*abs(map[x][y] - map[D.x][D.y]) / D.d; Q.push(Data(x, y, d, cost)); } } double BFS() { Data D(sx, sy, z, 0.0); Q.push(D); while (!Q.empty()) { D = Q.top(); Q.pop(); if (vis[D.x][D.y][D.d]==true) continue; else vis[D.x][D.y][D.d] = true; //第一次碰到就直接输出,因为一定是最短的 if (D.x == ex&&D.y == ey&&D.d) return D.cost; inputNeibourNode(D); } return -1; } int main() { int T; scanf("%d", &T); double ans; while (T--) { input(); init(); ans=BFS(); if (ans >= 0) printf("%.2lf\n", ans); else printf("No Answer\n"); } return 0; }
相关文章推荐
- 动易2006序列号破解算法公布
- Ruby实现的矩阵连乘算法
- C#插入法排序算法实例分析
- 超大数据量存储常用数据库分表分库算法总结
- C#数据结构与算法揭秘二
- C#冒泡法排序算法实例分析
- 算法练习之从String.indexOf的模拟实现开始
- C#算法之关于大牛生小牛的问题
- C#线程队列用法实例分析
- C#实现的算24点游戏算法实例分析
- 算法系列15天速成 第九天 队列
- c语言实现的带通配符匹配算法
- 浅析STL中的常用算法
- C语言单链队列的表示与实现实例详解
- 算法之排列算法与组合算法详解
- C++实现一维向量旋转算法
- Ruby实现的合并排序算法
- C#折半插入排序算法实现方法
- 基于C++实现的各种内部排序算法汇总
- C++线性时间的排序算法分析