题解 P6245 【[USACO06OPEN]The Climbing Wall S】
题解 P6245 【The Climbing Wall S】
核心算法:最短路
1.建模
我先来把问题简述一下: 在一个平面内,每个点都有其横坐标与纵坐标。Bessie想要从一个纵坐标不超过1000的点,爬到一个纵坐标为题目给定的H的点上。她每一步可以爬到与当前点距离不超过1000的点。问:最少爬多少步?
我们把题目中每一个落脚点就看作图中的点,把可以直接到达的两个点间建一条权值为1的边,代表走了一步。
那么跑一遍最短路,到终点的距离即为题目的答案。
2.细节
①建图
可能有些朋友已经注意到了,题目的起点和终点不唯一。
难道我们要跑多源最短路?那题目的数据范围显然是不允许的。
我们就要引入一种思想——虚拟点!
我自己乱起的名字
Bessie 的起点可以在任一高度不超过
1000 的落脚点上。
也就是说,所有纵坐标小于等于1000的点都可以作为起点。
那么我们可以建立一个虚拟起点,它到所有可以作为起点的点之间都建立一条边,权值为0(代表不增加次数)。这样,我们就可以以这个虚拟的起点为总起点跑单源最短路径了
但还有个问题,怎么确定终点呢?
一旦她到达了一个高度距离
H 不到1000 的落脚点,可以直接到墙顶。
很容易理解,墙顶就是我们的终点,但并不是一个横纵坐标都确定的点。那么,我们就可以自己把它建立出来!
所有纵坐标距离最大高度H不到1000的点我们都连上虚拟终点,是不是问题就简单了?
②距离处理
我相信这个其实不用说,你们也知道。
欧几里得距离公式:
③最短路
剩下的就是最短路模板了。
关于跑最短路,我还是秉持我一贯的观点:只要没有负边权,就用Dijkstra+堆优化。
关于SPFA,它死了
3.代码实现
我们前面一直只是纸上谈兵,那关键的操作如何实现呢?
很多朋友最短路相关问题,点和边的下标都是从1开始的。那么我们正好可以把0作为虚拟起点,把f+1作为虚拟终点。也很好理解不是吗?
当然也有朋友下标从0开始,其实把f+1作为虚拟起点,把f+2作为虚拟终点,也完全OK。
具体请见代码注释。
#include<cmath>
#include<cstdio>
#include<queue>
#include<algorithm>
#include<vector>
#define pii pair<int,int>//宏定义,个人习惯
using namespace std;
struct Node
{
int head,dis;
double x,y;
//横坐标和纵坐标
}node[10005];
struct Edge
{
int next,to,len;
}edge[99990005];
int h,f;
//与题目意义相同
bool cmp(Node a,Node b)
{
if(a.y==b.y)return a.x<b.x;
return a.y<b.y;
}
//排序函数,方便对点进行处理
double calc(double x_1,double y_1,double x_2,double y_2)
{
return double(sqrt((x_1-x_2)*(x_1-x_2)+(y_1-y_2)*(y_1-y_2)));
}
//求距离
int cnt;
void addEdge(int u,int v,int w)
{
edge[++cnt].len=w;
edge[cnt].next=node[u].head;
edge[cnt].to=v;
node[u].head=cnt;
}
//链式前向星存图
void Dijkstra()
{
for(int i=0;i<=f+1;i++) node[i].dis=0x3f3f3f3f;
//别忘了初始化
priority_queue<pii,vector<pii>,greater<pii> >q;
//STL小根堆
node[0].dis=0;
q.push({0,0});
while(q.size())
{
pii tmp=q.top();
q.pop();
int d=tmp.first,u=tmp.second;
if(d!=node[u].dis)continue;
for(int e=node[u].head;e;e=edge[e].next)
{
int v=edge[e].to;
if(node[v].dis>d+edge[e].len)
{
node[v].dis=d+edge[e].len;
q.push({node[v].dis,v});
}
}
}
}
//模板
int main()
{
scanf("%d%d",&h,&f);
for(int i=1;i<=f;i++)
{
scanf("%lf%lf",&node[i].x,&node[i].y);
}
sort(node+1,node+f+1,cmp);
//排序
for(int i=1;i<=f;i++)
{
for(int j=i+1;j<=f;j++)
{
double dist=calc(node[i].x,node[i].y,node[j].x,node[j].y);
//求得距离
if(dist<=1000)
{
//如果两个点间的距离不超过1000,
//就可以直接到达
//建立权值为1的边
//表示爬一次
addEdge(i,j,1);
addEdge(j,i,1);
}
}
if(h-node[i].y<1000)
{
//与最大距离H不到1000的点可以直接到达终点
//那么就与虚拟终点连一条边
addEdge(i,f+1,1);
addEdge(f+1,i,1);
//虚拟终点就是f+1
}
if(node[i].y<=1000)
{
//与地面距离不超过1000的点可以作为起点
//那么就与虚拟起点连一条边
addEdge(0,i,0);
addEdge(i,0,0);
//虚拟起点就是0
}
}
Dijkstra();
printf("%d\n",node[f+1].dis);
//这里注意输出的是虚拟终点的距离
return 0;
}
那么,题解到这里就结束了!
写题解不易,希望大家多多支持。如果还有不懂的可以at我或者私信我,我会尽力帮助的!