1138 : Islands Travel(题目分析+dijkstra求最短路)
时间限制:10000ms
单点时限:1000ms
内存限制:256MB
描述
There are N islands on a planet whose coordinates are (X1, Y1), (X2, Y2), (X3, Y3) ..., (XN, YN). You starts at the 1st island (X1, Y1) and your destination is the n-th island (XN, YN). Travelling between i-th and j-th islands will cost you min{|Xi-Xj|, |Yi-Yj|} (|a| denotes the absolute value of a. min{a, b} denotes the smaller value between a and b) gold coins. You want to know what is the minimum cost to travel from the 1st island to the n-th island.
输入
Line 1: an integer N.
Line 2~N+1: each line contains two integers Xi and Yi.
For 40% data, N<=1000,0<=Xi,Yi<=100000.
For 100% data, N<=100000,0<=Xi,Yi<=1000000000.
输出
Output the minimum cost.
样例输入
3 2 2 1 7 7 6
样例输出
2
嗯,寒假补题练习的第一道题…… 用时差不多是一天半。(药丸 题目拿到手先开始分析,题目的意思是,在一个星球上,存在n个岛屿,每个岛屿都可以和其他任意一个岛屿通行,但是花费是min(abs(xi-xj),abs(yi-yj)),即X轴上的差的绝对值(以后用距离称)和Y轴上两点的距离中的较小值。 问从第一个点到最后一个点的最小花费是多少。 开始推理,这道题每个岛屿之间的花费可以作为一个边的权值,那么这道题初看是一个求N阶完全图的单原最短路问题,很明显如果直接求N阶的话时间复杂度会爆炸(每个节点有N-1条边,那么就是N(N-1)/2条边,即10的10次方~10的9次方左右,对于每次需要遍历一个点的所有边的dijkstra算法来说画面美的不忍直视),当时想到这里的时候我就下意识的把图论的想法给否定了,这个是一个思维错误,详情见后 那既然不能用图论解决,就从贪心和dp下手试试,dp的想法被dijkstra给否定了,这个子问题实在是分的太大,在时间上造成了问题,而贪心,也就是说,最短路径上面的任何一个不包括终点的点,其下一个点必然是相连且边最小的点,这个可以通过下图否定:
如图可见,虽然B在距离起点较远,但是下一步到终点的花费比A要小
那么,如果按照贪心算法的话,必然是起点-A-B-终点,但是很明显A-B这个过程的花费是多余的,应在起点直接向A。
好了,贪心,dp,图论都否决了,怎么办?看题解吧……
然后看了题解以后就知道了,对于一个点来说,其作为原点的坐标系里,
X轴正负方向距离最近的两个点和Y轴正负方向最近的两个点构成一个集合,那么最短路的点一定存在在这个集合里面,我们证明一下:
如果这个结论是错的,那么对于一点来说,在任意一个象限中,存在一个不是距离最短的点v0,这个点存在于最短路L{S,v1,v2,v3,v4…E}中,若V1和S是在Y轴上比较近,那么在Y轴上一定存在一个离S更近的点m0(即上文提到的四个点之一),且这个点在Y轴方向上的V1和S之间:
上图红线标注的区域是m0可能存在的区域,可以看到,假如我们在S和v1之间插入一个点,把新的路径称为L’,那么L’与L的大小之比应该是求d(s,v1)与d(s,m0)+d(m0,v1)之差。
即abs(y_s-y_v1)?(abs(y_s-y_m0)+min(abs(y_v1-y_m0),abs(x_v1-x_m0))
显然问题的结果取决于abs(y_v1-y_m0)与abs(x_v1-x_m0)的大小关系
若abs(x_v1-x_m0)大于abs(y_v1-y_m0),那么结果即为m0到s的Y轴距离加上m0到v1的Y轴距离与原来s到y1的距离比较,那显然是相等的。
若abs(x_v1-x_m0)大于abs(y_v1-y_m0)——最极端的情况就是红色梯形的左上角——此时的m0直接导致s到v1的长度为0,此时L’小于L,这也就是说L并不是最短路,也意味着这个一个v1的点是不存在的。
那回到之前的话题,我们应该如何想到存在这么四个点——这四个好像从天上掉下来的点——本题中和以往的判断不同,对于两个点之间求得是x,y轴上坐标的最小值,那么应该可以想到,任意一个最小值都会在另外一个轴上带来位置上对于目标点的靠近,至于靠近的幅度是多少,我们是不知道的,所以x轴,y轴上需要找到两个距离最近的点,而那些距离远的点,如果是需要在对应轴上付出代价的话,那么从最近的点过去和从现在的轴过去所花费的代价是相同的。那为什么是4个?因为我们并不知道目标点是在我们现在所处位置的哪个方向,所以需要在四个象限上进行搜索,那么把x轴正向,负向,y轴正向,负向给查一遍,就可以得到所需的所有可能性了。
然后就是愉快的用优先队列+dijkstra搞事情(不过做题的时候因为优先队列不能重载<运算符对指针进行运算导致卡了一下去查度娘……得声明结构体以后使用三个参数的构建函数……)就可以过了,这道题目的启发的话……之前的思路不能完全放弃,注明不行的原因,看看能不能和后面想到的思路结合使用,比如我之前想到的图论,如果和贪心放在一起的话,说不定能够想出来?
果然还是有点勉强啊……
代码如下:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <sstream>
#include <string>
#include <algorithm>
#include <list>
#include <map>
#include <vector>
#include <queue>
#include <stack>
#include <cmath>
#include <cstdlib>
#include<climits>
#define MAX 100005
#define ll long long
using namespace std;
struct node
{
int x, y;
int id;
bool vst = 0;
ll path = INT_MAX;
};
struct cmp {
bool operator() (node *a, node *b)
{
return a->path > b->path;
}
};
inline int reabs(node &a, node &b)
{
return min(abs(a.x - b.x), abs(a.y - b.y));
}
int comp1(node a, node b)
{
return a.x < b.x;
}
int comp2(node a, node b)
{
return a.y < b.y;
}
vector<node>sto;
vector<vector<int>>ac;
vector<node>ori;
vector<bool>vst;
void dij()
{
priority_queue<node*,vector<node*>,cmp>Q;
ori[0].path = 0;
Q.push(&ori[0]);
ori[0].vst = 1;
while (!Q.empty())
{
node*temp = Q.top();
Q.pop();
for (int i = 0; i < ac[temp->id].size(); i++)
{
node *obj = &ori[ac[temp->id][i]];//取出相连节点
int dis = reabs(*temp, *obj);
if (dis + temp->path < obj->path)//小于则更新
obj->path = dis + temp->path;
if (obj->vst == 0)//若该节点第一次遇到,加入优先队列
{
obj->vst = 1;
Q.push(obj);
}
}
}}
int main()
{
int N;
cin >> N;
sto.resize(N);
ac.resize(N);
vst.resize(N,0);
for (int i = 0; i < N; i++)
{
scanf("%d %d", &sto[i].x, &sto[i].y);
sto[i].id = i;
sto[i].vst = 0;
}
ori = sto;
sort(sto.begin(), sto.end(), comp1);
for (int i = 1; i < N ; i++)
{
ac[sto[i].id].push_back(sto[i - 1].id);
ac[sto[i - 1].id].push_back(sto[i].id);
}
sort(sto.begin(), sto.end(), comp2);
for (int i = 1; i < N; i++)
{
ac[sto[i].id].push_back(sto[i - 1].id);
ac[sto[i - 1].id].push_back(sto[i].id);
}
dij();
cout<<ori[N-1].path;
return 0;
}