单元最短路入门及相应例题

前言

单源最短路指的是在一张有向图中从起点 a a a 走到重点 b b b 的最小花费。

Dijkstra算法

洛谷模板

简介

该算法是从起始点开始,采用贪心方法,每次找到最近且还没被访问过的点作为起点并从该点向其他还没访问过的点查询。

详解

开始将除了初始点外的所有点设为无穷大,且图如下图所示。不妨令 a i a_i ai 表示从起始点到 i i i 的最小花费,可得:

i i i 1 1 1 2 2 2 3 3 3 4 4 4
a i a_i ai 0 0 0 + ∞ +\infty + + ∞ +\infty + + ∞ +\infty +

在这里插入图片描述

首次遍历,从 1 1 1 开始依次走向其它 3 3 3 个点,更新 a i a_i ai

i i i 1 1 1 2 2 2 3 3 3 4 4 4
a i a_i ai 0 0 0 2 2 2 5 5 5 4 4 4

接着找到 2 2 2 为没遍历过最小的,更新

i i i 1 1 1 2 2 2 3 3 3 4 4 4
a i a_i ai 0 0 0 2 2 2 4 4 4 3 3 3

总结

可以发现,我们每次的工作如下:

  • 找到一个未被标记的最小点 m i n v minv minv 并将其用 v v v 打上标记。
  • m i n v minv minv 出发,更新所有的 k k t o kk_{to} kkto,也就是上表所示的 a i a_i ai
#include<bits/stdc++.h>
using namespace std;
struct edge{
	int to,w;
}e[1000005];
int head[200005],en,nex[1000005];
bool v[200005];
void add(int x,int y,int ww){
	nex[++en] = head[x];
	head[x] = en;
	e[en].to = y;
	e[en].w = ww;
	return;   
}
int kk[200005];
int main() {
	int n,m,s;
	scanf("%d%d%d",&n,&m,&s);
	for(int i = 1;i <= m;i++) {
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		add(u,v,w);		
	}
	for(int i = 1;i <= n;i++) {
		kk[i] = pow(2,31) - 1;
	}
	kk[s] = 0;
	for(int i = 1;i <= n;i++) {
		int minn = pow(2,31) - 1,minv;
		for(int j = 1;j <= n;j++) {
			if(!v[j] && minn > kk[j]) {
				minn = kk[j];
				minv = j;
			}
		}
		v[minv] = true;
		for(int j = head[minv];j;j = nex[j]){
			if(!v[e[j].to]) {
				kk[e[j].to] = min(kk[e[j].to],kk[minv] + e[j].w);
			}
		}
	}
	for(int i = 1;i <= n;i++) {
		printf("%d ",kk[i]);
	}
}

优化

上面算法复杂度为 O ( N 2 ) O(N^2) O(N2),考虑采用优先队列。推荐大家先阅读一下两篇文章:CSDN-C++——优先级队列(priority_queue)【C++】结构体使用及运算符重载

对于每次更新 k k i kk_i kki,考虑将 i , k i i,k_i i,ki 赋值为一个结构体,并用 k k i kk_i kki 降序排列,每次取出最上面一个,时间复杂度 O ( N l o g N ) O(N log N) O(NlogN)

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m,s;
int head[100005],nex[400005],cnt = 0,a[100005];
bool vis[100005];
struct edge{
	int to,w;
}node[400005];
void add(int x,int y,int z) {
	nex[++cnt] = head[x];
	head[x] = cnt;
	node[cnt].to = y;
	node[cnt].w = z;
} 
struct point{
	int id,w;
	friend bool operator <(point x,point y) {
		return x.w > y.w;
	}
};
priority_queue<point>p; 
signed main() {
	scanf("%lld %lld %lld",&n,&m,&s);
	for(int i = 1;i <= n;i++) a[i] = 2147483647;
	a[s] = 0;
	point b;
	b.id = s,b.w = 0;
	p.push(b);
	for(int i = 1;i <= m;i++) {
		int x,y,z;
		scanf("%lld %lld %lld",&x,&y,&z);
		add(x,y,z);
	}
	while(!p.empty()) {
		point now = p.top();
		p.pop();
		if(vis[now.id]) continue;
		vis[now.id] = 1;
		for(int i = head[now.id];i;i = nex[i]) {
			if(a[node[i].to] > a[now.id] + node[i].w) {
				a[node[i].to] = a[now.id] + node[i].w;
				point c;
				c.id = node[i].to;
				c.w = a[now.id] + node[i].w;
				p.push(c);
			}
		}
	}
	for(int i = 1;i <= n;i++) printf("%lld ",a[i]);
    return 0;
}

在这里插入图片描述

例题

P1027 [NOIP2001 提高组] Car 的旅行路线
这题操作较为复杂,尽管是四个源点,操作方法却和上文相同。注意矩形的边不一定与坐标轴平行,需要用勾股定理判断出直角边后在进行操作。

#include<bits/stdc++.h>
#define int long long
using namespace std;
double dis(double x_1,double y_1,double x_2,double y_2) {
	return (x_1 - x_2) * (x_1 - x_2) + (y_1 - y_2) * (y_1 - y_2);
}
int head[405],nex[500005],cnt;
double x[405],y[405],T[405];
struct edge{
	int to;
	double w;
}node[500005];
double t;
int s,a,b;
void add(int p1,int p2,double w) {
	nex[++cnt] = head[p1];
	head[p1] = cnt;
	node[cnt].to = p2;
	node[cnt].w = w;
} 
struct point{
	int id;
	double w;
	friend bool operator <(point x_1,point y_1) {
		return x_1.w > y_1.w;
	}
};
priority_queue<point>p;
double ans[405];
signed main() {
	ios::sync_with_stdio(false);//取消输入输出流和stdio流的同步
    cin.tie(0);//解除cin和cout的绑定
	int n;
	cin>>n;
	while(n--) {
		//预处理 
		cnt = 0;
		for(int i = 0;i <= 400;i++) ans[i] = 10000000.0,head[i] = 0,x[i] = 0,y[i] = 0,T[i] = 0;
		for(int i = 0;i <= 500000;i++) nex[i] = 0,node[i].to = 0,node[i].w = 0;
		//输入 
		cin>>s>>t>>a>>b;
		for(int i = 0;i < s;i++) {
			cin>>x[i * 4 + 1]>>y[i * 4 + 1];
			cin>>x[i * 4 + 2]>>y[i * 4 + 2];
			cin>>x[i * 4 + 3]>>y[i * 4 + 3];
			cin>>T[i];
			double dis1 = dis(x[i * 4 + 2],y[i * 4 + 2],x[i * 4 + 3],y[i * 4 + 3]);
			double dis2 = dis(x[i * 4 + 1],y[i * 4 + 1],x[i * 4 + 3],y[i * 4 + 3]);
			double dis3 = dis(x[i * 4 + 1],y[i * 4 + 1],x[i * 4 + 2],y[i * 4 + 2]);
		//	cout<<dis1<<" "<<dis2<<" "<<dis3<<endl;
			if(dis1 + dis2 == dis3) {
				x[i * 4] = x[i * 4 + 1] + x[i * 4 + 2] - x[i * 4 + 3];
				y[i * 4] = y[i * 4 + 1] + y[i * 4 + 2] - y[i * 4 + 3];
			}
			if(dis1 + dis3 == dis2) {
				x[i * 4] = x[i * 4 + 1] + x[i * 4 + 3] - x[i * 4 + 2];
				y[i * 4] = y[i * 4 + 1] + y[i * 4 + 3] - y[i * 4 + 2];
			}
			if(dis2 + dis3 == dis1) {
				x[i * 4] = x[i * 4 + 2] + x[i * 4 + 3] - x[i * 4 + 1];
				y[i * 4] = y[i * 4 + 2] + y[i * 4 + 3] - y[i * 4 + 1];
			}
		}
		for(int i = 0;i <= s * 4 - 2;i++) {
		//	cout<<x[i]<<" "<<y[i]<<endl;
			for(int j = i + 1;j <= s * 4 - 1;j++) {
				double d = sqrt(dis(x[i],y[i],x[j],y[j])); 
				//printf("%lld %lld %.1lf\n",i,j,d);
				if(i / 4 == j / 4) add(i,j,d * T[i / 4]),add(j,i,d * T[i / 4]);
				else add(i,j,d * t),add(j,i,d * t);
			}
		}
		point start;
		start.w = 0;
		for(int i = 4 * (a - 1);i < 4 * a;i++) start.id = i,p.push(start);
		//dij
		while(!p.empty()) {
			start = p.top();
			p.pop();
			if(ans[start.id] != 10000000.0) continue;
			ans[start.id] = start.w;
			//printf("%lld %.1lf:\n",start.id,ans[start.id]);
			for(int i = head[start.id];i;i = nex[i]) {
				//printf("%lld %.1lf %.1lf\n",node[i].to,node[i].w,ans[node[i].to]);
				if(ans[node[i].to] > ans[start.id] + node[i].w) {
					point new_point;
					new_point.id = node[i].to;
					new_point.w = ans[start.id] + node[i].w;
					p.push(new_point);
					//printf("__________\n");
				}
			}
		}
		double min_ans = 10000000.0;
		for(int i = 4 * b - 4;i < 4 * b;i++) {
			if(min_ans > ans[i]) min_ans = ans[i];
		}
		printf("%.1lf\n",min_ans);
	}
    return 0;
}

参考网站

百度百科-单源最短路径
CSDN-图论:Dijkstra算法——最详细的分析,图文并茂,一次看懂!

  • 23
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值