📒博客首页:永遇乐金枪鱼的博客
🎉欢迎关注🔎点赞👍收藏⭐️留言📝
❤️ :热爱Java与算法学习,期待一起交流!
🙏作者水平很有限,如果发现错误,求告知,多谢!
🌺有问题可私信交流!!!
👻高校算法学习社区:高校算法学习社区
一起加入刷题内卷大军,还可以加入专属内卷群
目录
P1744 采购特价商品 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
题目描述:中山路上有n(n<=100)家店,每家店的坐标均在-10000~10000之间。其中的m家店之间有通路。若有通路,则表示可以从一家店走到另一家店,通路的距离为两点间的直线距离。
输入格式:
共n+m+3行:
第1行:整数n
第2行~第n+1行:每行两个整数x和y,描述了一家店的坐标
第n+2行:整数m
第n+3行~第n+m+2行:每行描述一条通路,由两个整数i和j组成,表示第i家店和第j家店之间有通路。
第n+m+3行:两个整数s和t,分别表示原点和目标店
输出格式:
仅一行:一个实数(保留两位小数),表示从s到t的最短路径长度。
数据量:n<=100,m<=1000
分析:题目给定店的坐标以及两家店之间是否有路,所以需要用到两点距离公式,距离或着由公式可以得到这个权值为非负,并且这个距离可以为小数,所以应该用double声明;题目说到“若有通路,则表示可以从一家店走到另一家店”,说明了这是无向图。由于输出只有一行(从s到t的最短路径长度),则说明给定两个点求其最短路径,那么可以认为给定一个图G和1个源点s,对于图中任意点v,求s到v的最短距离。由数据量及第四大点的各算法复杂度可得,五种方法都能被用于解决这道题。
下面是5种方法解答本题(两点距离公式只在第一种处写了,完整代码给出第一第二种方法,剩下三种给出核心伪代码供大家思考):
Dijkstra朴素版
声明一个f[u][v]用于存放点u到v的距离,无向图则用f[u][v]=f[v][u],dis[i]用于存放始点到点i的最短距离,两个数组都需要初始化成无穷大,然后寻找距离始点最近且未访问过的顶点的邻接节点,如果可以松弛则进行松弛操作。
#include <bits/stdc++.h>
#define INF 0x7f7f7f7f
using namespace std;
typedef long long LL;
double dis[105];
double f[105][105];
int n,m,s,t;
int vis[105],x[105],y[105];
inline int read() {
int date=0,w=1;
char c=getchar();
while(c<'0' || c>'9') {
if(c=='-') w=-1;
c=getchar();
}
while(c>='0' && c<='9') {
date=date*10+(c-'0');
c=getchar();
}
return date*w;
}
double dist(int a,int b) {
double ss=sqrt(abs(x[a]-x[b])*abs(x[a]-x[b])+abs(y[a]-y[b])*abs(y[a]-y[b]));
return ss;
}
void dijkstra() {
dis[s]=0.00;
for(int i=1; i<=n; i++) {
int t=-1;
for(int j=1; j<=n; j++)
if(!vis[j]&&(t==-1 || dis[j]<dis[t])) t=j;
vis[t]=1;
for(int j=1; j<=n; j++)
if(dis[j]>dis[t]+f[t][j]) dis[j]=dis[t]+f[t][j];
}
}
int main() {
int h;
n=read();
for(int i=1; i<=n; i++)
x[i]=read(),y[i]=read();
m=read();
memset(dis,INF,sizeof(dis));
memset(f,INF,sizeof(f));
for(int i=1; i<=m; i++) {
int a=read(),b=read();
double c=dist(a,b);
f[b][a]=f[a][b]=c;
}
s=read(),h=read();
dijkstra();
printf("%.2f",dis[h]);
return 0;
}
Dijkstra堆优化版
#include <bits/stdc++.h>
#define INF 0x7f7f7f7f
using namespace std;
typedef long long LL;
double dis[105],w[2500];
int e[2500],h[105],ne[2500];
int idx,n,m,s,t;
int vis[105],x[105],y[105];
struct node {
int x;
double d;
bool operator < (node p) const {
return d > p.d;
}
node(int x,double d):x(x),d(d) {}
};
inline int read() {
int date=0,w=1;
char c=getchar();
while(c<'0' || c>'9') {
if(c=='-') w=-1;
c=getchar();
}
while(c>='0' && c<='9') {
date=date*10+(c-'0');
c=getchar();
}
return date*w;
}
void add(int a,int b,double c) {
idx++;
e[idx]=b;
w[idx]=c;
ne[idx]=h[a];
h[a]=idx;
}
void dijkstra() {
priority_queue<node> q;
memset(dis,INF,sizeof(dis));
dis[s]=0;
node u(s,dis[s]);
q.push(u);
while(!q.empty()) {
node u=q.top();
q.pop();
if(vis[u.x]) continue;
vis[u.x]=1;
for(int i=h[u.x]; i; i=ne[i]) {
if(dis[e[i]] > dis[u.x] + w[i]) {
dis[e[i]] = dis[u.x] + w[i];
node v(e[i],dis[e[i]]);
q.push(v);
}
}
}
}
int main() {
n=read();
for(int i=1; i<=n; i++)
x[i]=read(),y[i]=read();
m=read();
for(int i=1; i<=m; i++) {
int a=read(),b=read();
double c=dist(a,b);
add(a,b,c);
add(b,a,c);
}
s=read(),t=read();
dijkstra();
printf("%.2f",dis[t]);
return 0;
}
bellman-ford算法
初始化dis为无穷大
dis[起点]=0
for i=1 to n-1
for j=1 to m
判定是否符合松弛操作条件
如果是则进行
for i=1 to m
判定是否符合松弛操作条件
如果是则有负权回路,否则输出答案
spfa(与dijkstra堆优化基本一样)
初始化dis为无穷大
定义一个存储int类型的队列
初始化dis[起点]=0
标记起点
将起点压入队列
队列非空
取出队头元素并弹出
vis[队头元素]=0
for i=h[队头元素]; i是非空指针; i <- i的下一条边
if dis[第i条边的后驱] <- dis[队头] + w[i]
dis[第i条边的后驱] <- dis[队头] + w[i]//松弛操作
if 当前边的后驱还未被标记
标记该后驱
将该后驱压入队列
floyd
初始化f为无穷大
for i=1 to n
f[i][i] <- 0
for k=1 to n
for i=1 to n
for j=1 to n
if f[i][j] > f[i][k] + f[k][j]
f[i][j] <- f[i][k] + f[k][j]
补充
第二第四种方法需要用到邻接表,这里给出伪代码及模板
伪代码:
构建邻接表的方式(idx用于表示当前为第idx条边):
e[++idx] <- 当前边的后驱
w[idx] <- 当前边的权值
ne[idx] <- h[当前边的前驱]
h[当前边的前驱] <- idx
模板:
void add(int a,int b,double c) {
idx++;
e[idx]=b;
w[idx]=c;
ne[idx]=h[a];
h[a]=idx;
}