您的位置:首页 > 其它

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(请不要嘲笑本人的绘图工具与水平),其余都标在这个上面



我们首先操作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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  bfs bestcoder acm 算法 队列