原题链接
题目大意
平面上有 n n n个点,每个点的坐标均在 − 10000 -10000 −10000到 10000 10000 10000之间。其中的一些点之间有连线。若有连线,则表示可从一个点到达另一个点,即两点间有通路,通路的距离为两点直线的距离。现在的任务是找出从一点到另一点之间的最短路径。
输入格式
输入共有
n
+
m
+
3
n+m+3
n+m+3行,其中:
第一行为一个整数
n
n
n。
第
2
2
2行到第
n
+
1
n+1
n+1行(共
n
n
n行),每行的两个整数
x
x
x和
y
y
y,描述一个点的坐标(以一个空格隔开)。
第
n
+
2
n+2
n+2行为一个整数
m
m
m,表示图中的连线个数。
此后的
m
m
m行,每行描述一条连线,由两个整数
i
,
j
i,j
i,j组成,表示第
i
i
i个点和第
j
j
j个点之间有连线。
最后一行:两个整数
s
s
s和
t
t
t,分别表示源点和目标点。
输出格式
输出仅一行,一个实数(保留两位小数),表示从 s s s到 t t t的最短路径的长度。
S a m p l e \mathbf{Sample} Sample I n p u t \mathbf{Input} Input
5
0 0
2 0
2 2
0 2
3 1
5
1 2
1 3
1 4
2 5
3 5
1 5
S a m p l e \mathbf{Sample} Sample O u t p u t \mathbf{Output} Output
3.41
H
i
n
t
&
E
x
p
l
a
i
n
\mathbf{Hint\&Explain}
Hint&Explain
样例数据如图所示。
先从
1
1
1号点到
3
3
3号点,再到
5
5
5号点,总共路程长度为
2
+
2
≈
3.41
2+\sqrt{2} \approx 3.41
2+2≈3.41。
数据范围
对于 100 % 100\% 100%的数据满足: 1 ≤ n ≤ 100 1 \le n \le 100 1≤n≤100。
解题思路
此题可以用三种方法来解。
F
l
o
y
e
d
−
W
a
r
s
h
a
l
l
Floyed-Warshall
Floyed−Warshall做法
F
l
o
y
e
d
−
W
a
r
s
h
a
l
l
Floyed-Warshall
Floyed−Warshall算法(以下简称
F
l
o
y
e
d
Floyed
Floyed)其实就是一种贪心做法,枚举其中的一个点作为中转点,再判断是否为更优路径。
如上图,当我们求
1
1
1到
5
5
5的距离时,我们可以枚举中转点,把原来的
1
1
1到
5
5
5的距离 和后来加上中转点后
1
1
1到
3
3
3的距离加上
3
3
3到
5
5
5的距离 作比较,就可以求出更优距离。
大体的框架如下:
输入时建边
for(枚举中转点)
for(枚举开始点)
for(枚举结束点)
dist[开始][结束]=max(dist[开始][结束],dist[开始][中转点]+dist[中转点][结束]);
输出
时间复杂度
Θ
(
n
3
)
\Theta(n^3)
Θ(n3)。
核心代码:
for(int k=1; k<=n; k++)
for(int i=1; i<=n; i++)
for(int j=1; j<=n; j++)
dist[i][j]=min(dist[i][j],dist[i][k]+dist[k][j]);
D
i
j
k
s
t
r
a
Dijkstra
Dijkstra算法
D
i
j
k
s
t
r
a
Dijkstra
Dijkstra算法是一种单源最短路径算法。也就是说,只能计算起点只有一个的情况。
核心思想:
从某一个点出发,依次寻找离他和已经经过的点最近的点,把他标记,此时他的值就是起点到当前点的最短路径。
让我们用图来尝试理解一下。
首先,造一个和样例一样的图。
设
d
i
s
i
dis_i
disi为
i
i
i点到最近的蓝点的路径长度,
v
i
s
i
vis_i
visi代表
i
i
i点有没有标蓝,
d
i
d_i
di代表从出发点到
i
i
i点的最短路径长度。
初始时
d
i
s
dis
dis数组为
[
0
,
∞
,
∞
,
∞
,
∞
]
[0,\infty,\infty,\infty,\infty]
[0,∞,∞,∞,∞],
v
i
s
vis
vis数组为
[
F
,
F
,
F
,
F
,
F
]
[F,F,F,F,F]
[F,F,F,F,F],
d
d
d数组为
[
∞
,
∞
,
∞
,
∞
,
∞
]
[\infty,\infty,\infty,\infty,\infty]
[∞,∞,∞,∞,∞]。
让我们把一号点作为出发点。
此时
d
i
s
dis
dis数组为
[
0
,
2
,
2
2
,
2
,
∞
]
[0,2,2\sqrt2,2,\infty]
[0,2,22,2,∞],
v
i
s
vis
vis数组为
[
T
,
F
,
F
,
F
,
F
]
[T,F,F,F,F]
[T,F,F,F,F],
d
d
d数组为
[
0
,
∞
,
∞
,
∞
,
∞
]
[0,\infty,\infty,\infty,\infty]
[0,∞,∞,∞,∞]。
此时,离已经标蓝的点最近的点是
2
2
2号,长为
2
2
2,把
2
2
2号点标蓝,
v
i
s
2
vis_2
vis2改为
T
T
T。
此时
d
i
s
dis
dis数组为
[
0
,
2
,
2
2
,
2
,
2
]
[0,2,2\sqrt2,2,\sqrt2]
[0,2,22,2,2],
v
i
s
vis
vis数组为
[
T
,
T
,
F
,
F
,
F
]
[T,T,F,F,F]
[T,T,F,F,F],
d
d
d数组为
[
0
,
2
,
∞
,
∞
,
∞
]
[0,2,\infty,\infty,\infty]
[0,2,∞,∞,∞]。
此时,离已经标蓝的点最近的点是
5
5
5号,长为
2
\sqrt2
2,把
5
5
5号点标蓝,
v
i
s
5
vis_5
vis5改为
T
T
T。
此时
d
i
s
dis
dis数组为
[
0
,
2
,
2
,
2
,
2
]
[0,2,\sqrt2,2,\sqrt2]
[0,2,2,2,2],
v
i
s
vis
vis数组为
[
T
,
T
,
F
,
F
,
T
]
[T,T,F,F,T]
[T,T,F,F,T],
d
d
d数组为
[
0
,
2
,
∞
,
∞
,
2
+
2
]
[0,2,\infty,\infty,2+\sqrt2]
[0,2,∞,∞,2+2]。
此时,离已经标蓝的点最近的点是
3
3
3号,长为
2
\sqrt2
2,把
3
3
3号点标蓝,
v
i
s
3
vis_3
vis3改为
T
T
T。
此时
d
i
s
dis
dis数组为
[
0
,
2
,
2
,
2
,
2
]
[0,2,\sqrt2,2,\sqrt2]
[0,2,2,2,2],
v
i
s
vis
vis数组为
[
T
,
T
,
T
,
F
,
T
]
[T,T,T,F,T]
[T,T,T,F,T],
d
d
d数组为
[
0
,
2
,
2
+
2
2
,
∞
,
2
+
2
]
[0,2,2+2\sqrt2,\infty,2+\sqrt2]
[0,2,2+22,∞,2+2]。
此时,离已经标蓝的点最近的点是
4
4
4号,长为
2
2
2,把
2
2
2号点标蓝,
v
i
s
2
vis_2
vis2改为
T
T
T。
此时
d
i
s
dis
dis数组为
[
0
,
2
,
2
,
2
,
2
]
[0,2,\sqrt2,2,\sqrt2]
[0,2,2,2,2],
v
i
s
vis
vis数组为
[
T
,
T
,
T
,
T
,
T
]
[T,T,T,T,T]
[T,T,T,T,T],
d
d
d数组为
[
0
,
2
,
2
+
2
2
,
2
,
2
+
2
]
[0,2,2+2\sqrt2,2,2+\sqrt2]
[0,2,2+22,2,2+2]。
到此,算法结束。 d 5 d_5 d5就是从出发点到 5 5 5号点的距离,为 2 + 2 ≈ 3.41 2+\sqrt2\approx 3.41 2+2≈3.41。
时间复杂度 Θ ( n 2 ) \Theta(n^2) Θ(n2)。
核心代码:
for(int i=1; i<n; i++)
{
double minn=0x7fffffff;
int id;
for(int j=1; j<=n; j++)
if(!vis[j]&&dist[j]<minn)
minn=dist[j],id=j;
vis[id]=true;
for(int j=0; j<road[id].size(); j++)
{
int temp=road[id][j];
if(!vis[temp])
dist[temp]=min(dist[temp],dist[id]+dis(point[id],point[temp]));
}
}
注 意 : D i j k s t r a 不 能 处 理 存 在 负 边 权 的 情 况 ! \large{{\color{red}{注意:Dijkstra不能处理存在负边权的情况!}}} 注意:Dijkstra不能处理存在负边权的情况!
堆优化的
D
i
j
k
s
t
r
a
Dijkstra
Dijkstra算法
上面的
D
i
j
k
s
t
r
a
Dijkstra
Dijkstra算法的时间复杂度为
Θ
(
n
2
)
\Theta(n^2)
Θ(n2),还是非常耗时,如果用堆优化的话,时间复杂度可以降到
Θ
(
n
log
2
n
)
\Theta(n\log_2 n)
Θ(nlog2n)。
这里要用到的是优先队列:priority_queue
。
优先队列默认的是大根堆,即从大到小的一个堆,但是在这里我们要用的是把权值从小到大排序的小根堆,应该如何转化呢?
根据在数轴上一个数越大,他的相反数就越小的道理,即5
和3
的相反数为-5
和-3
,但是大小从原来的5 > 3
变成了-5 < -3
。
因此,我们只需要在权值上面取一个相反数,就可以把大根堆变成小根堆。
值得注意的是:在每一次一个点被加入了队列之后,有可能他已经被修改过了,但是由于他在队列里面的原因,这个新值并没有加入队列。因此,我们要在当前权值和当前答案不一致的情况下直接continue
。
时间复杂度
Θ
(
(
n
+
m
)
log
2
n
)
\Theta((n+m)\log_2n)
Θ((n+m)log2n)。
核心代码:
while(pq.size())
{
pair<double,int> now=pq.top();
pq.pop();
double val=-now.first;
int id=now.second;
// vis[id]=true;
if(dist[id]!=val)
continue;
for(int i=0; i<road[id].size(); i++)
{
int temp=road[id][i];
if(dist[temp]>dist[id]+dis(point[id],point[temp]))
{
dist[temp]=dist[id]+dis(point[id],point[temp]);
pq.push(make_pair(-dist[temp],temp));
}
}
}
最后,祝大家早日
上代码
C o d e 1 Code\ 1 Code 1
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
typedef pair<double,double> state;
double dist[110][110];
double d[110][110];
state point[110];
int f,t;
int n,m;
double dis(state x,state y)
{
return sqrt(pow(x.first-y.first,2.00)+pow(x.second-y.second,2.00));
}
int main()
{
cin>>n;
for(int i=1; i<=n; i++)
{
double x,y;
cin>>x>>y;
point[i]=make_pair(x,y);
}
for(int i=1; i<=n; i++)
for(int j=1; j<=n; j++)
dist[i][j]=0x3f3f3f3f;
cin>>m;
for(int i=1; i<=m; i++)
{
int x,y;
cin>>x>>y;
dist[x][y]=dist[y][x]=dis(point[x],point[y]);
}
cin>>f>>t;
for(int k=1; k<=n; k++)
for(int i=1; i<=n; i++)
for(int j=1; j<=n; j++)
dist[i][j]=min(dist[i][j],dist[i][k]+dist[k][j]);
printf("%0.2lf\n",dist[f][t]);
// for(int i=1; i<=n; i++)
// {
// for(int j=1; j<=n; j++)
// printf("%0.2lf ",dist[i][j]);
// cout<<endl;
// }
return 0;
}
C o d e 2 Code\ 2 Code 2
#include<iostream>
#include<cstdio>
#include<vector>
#include<cmath>
using namespace std;
typedef pair<double,double> state;
vector<int> road[110];
double dist[110];
state point[110];
bool vis[110];
int n,m,f,t;
double dis(state x,state y)
{
return sqrt(pow(x.first-y.first,2.00)+pow(x.second-y.second,2.00));
}
int main()
{
cin>>n;
for(int i=1; i<=n; i++)
{
double x,y;
cin>>x>>y;
point[i]=make_pair(x,y);
}
cin>>m;
for(int i=1; i<=m; i++)
{
int x,y;
cin>>x>>y;
road[x].push_back(y);
road[y].push_back(x);
}
cin>>f>>t;
for(int i=1; i<=n; i++)
dist[i]=0x3f3f3f3f;
dist[f]=0;
int line_tot=0;
for(int i=1; i<n; i++)
{
double minn=0x7fffffff;
int id;
for(int j=1; j<=n; j++)
if(!vis[j]&&dist[j]<minn)
minn=dist[j],id=j;
vis[id]=true;
for(int j=0; j<road[id].size(); j++)
{
int temp=road[id][j];
if(!vis[temp])
dist[temp]=min(dist[temp],dist[id]+dis(point[id],point[temp]));
}
}
printf("%0.2lf\n",dist[t]);
return 0;
}
C o d e 3 Code\ 3 Code 3
#include<iostream>
#include<cstdio>
#include<vector>
#include<cmath>
#include<queue>
using namespace std;
typedef pair<double,double> state;
priority_queue<pair<double,int> > pq;
vector<int> road[110];
double dist[110];
state point[110];
bool vis[110];
int n,m,f,t;
double dis(state x,state y)
{
return sqrt(pow(x.first-y.first,2.00)+pow(x.second-y.second,2.00));
}
int main()
{
cin>>n;
for(int i=1; i<=n; i++)
{
double x,y;
cin>>x>>y;
point[i]=make_pair(x,y);
}
cin>>m;
for(int i=1; i<=m; i++)
{
int x,y;
cin>>x>>y;
road[x].push_back(y);
road[y].push_back(x);
}
cin>>f>>t;
for(int i=1; i<=n; i++)
dist[i]=0x7fffffff;
dist[f]=0;
pq.push(make_pair(0,f));
while(pq.size())
{
pair<double,int> now=pq.top();
pq.pop();
double val=-now.first;
int id=now.second;
// vis[id]=true;
if(dist[id]!=val)
continue;
for(int i=0; i<road[id].size(); i++)
{
int temp=road[id][i];
if(dist[temp]>dist[id]+dis(point[id],point[temp]))
{
dist[temp]=dist[id]+dis(point[id],point[temp]);
pq.push(make_pair(-dist[temp],temp));
}
}
}
printf("%0.2lf\n",dist[t]);
return 0;
}
完美切题 ∼ \sim ∼