最短路与差分约束

  题目链接:vjudge.net/contest/246705

本次博客以A题为例,分别采取四种方法解决最短路问题。

                                   A - Til the Cows Come Home

 

Bessie is out in the field and wants to get back to the barn to get as much sleep as possible before Farmer John wakes her for the morning milking. Bessie needs her beauty sleep, so she wants to get back as quickly as possible. 

Farmer John's field has N (2 <= N <= 1000) landmarks in it, uniquely numbered 1..N. Landmark 1 is the barn; the apple tree grove in which Bessie stands all day is landmark N. Cows travel in the field using T (1 <= T <= 2000) bidirectional cow-trails of various lengths between the landmarks. Bessie is not confident of her navigation ability, so she always stays on a trail from its start to its end once she starts it. 

Given the trails between the landmarks, determine the minimum distance Bessie must walk to get back to the barn. It is guaranteed that some such route exists.

Input

* Line 1: Two integers: T and N 

* Lines 2..T+1: Each line describes a trail as three space-separated integers. The first two integers are the landmarks between which the trail travels. The third integer is the length of the trail, range 1..100.

Output

* Line 1: A single integer, the minimum distance that Bessie must travel to get from landmark N to landmark 1.

Sample Input

5 5
1 2 20
2 3 30
3 4 20
4 5 20
1 5 100

Sample Output

90

Hint

INPUT DETAILS: 

There are five landmarks. 

OUTPUT DETAILS: 

Bessie can get home by following trails 4, 3, 2, and 1.

 

第一种方法,djk1,易懂,但是时间复杂度为n^2,一般数据大一点就跑不动了,用来对迪杰斯特拉算法的初步理解。

#include <stdio.h>
#include <queue>
#include <string.h>
using namespace std;

// <2> djk求单源最短路,邻接矩阵存图 时间复杂度o(n^2)

const int N = (int) 1000 + 11; // 最大点数
const int INF = (int) 0x3f3f3f3f;

int n, m; // n是点的个数,m是边的个数
int mp[N][N];
void init(int n){// 初始化
	for(int i = 0; i < n; i++){
		for(int j = 0; j < n; j++){
			if(i == j) mp[i][j] = 0;
			else mp[i][j] = mp[j][i] = INF;
		}
	}
}
void getmap(int m){ // 建图
	int u, v, val;
	while(m--){
		scanf("%d%d%d", &u, &v, &val);
		u--; v--;
		mp[u][v] = mp[v][u] = min(mp[u][v], val); // 如果是双向图写这句
		// mp[u][v] = min(mp[u][v], val); // 如果是单向图写这句
	}
}
bool vis[N]; int dis[N];
void djk(int st, int ed){ // 核心代码,点的序号是从0-(n-1)
	for(int i = 0; i < n; i++){
		vis[i] = false;
		dis[i] = mp[st][i];
	}
	vis[st] = true;
	for(int i = 1; i < n; i++){
		int mn = INF, id = -1;  // 找到离起点最近的未访问过得点
		for(int j = 0; j < n; j++){
			if(!vis[j] && dis[j] < mn) {
				mn = dis[j]; id = j;
			}
		}
		if(id == -1) break;
		vis[id] = true; // 标记
		for(int j = 0; j < n; j++){
			if(!vis[j] && mp[id][j] != INF) { // 如果未访问且 id->j有边
				if(dis[j] > dis[id] + mp[id][j])  // 是否可以松弛
					dis[j] = dis[id] + mp[id][j];
			}
		}
	}
	printf("%d\n", (dis[ed] == INF) ? -1 : dis[ed]);
}
int main(){
	int t; scanf("%d%d", &t, &n);
	init(n);
	getmap(t);
	djk(n - 1, 0);
	return 0;
}

 

第二种,djk2,djk1的优化版,时间复杂度o(E * log(V)),是一种常用的算法,稳定不易出错。

(需要对优先队列,pair,链式前向星有一定的理解)

#include <stdio.h>
#include <queue>
#include <string.h>

using namespace std;

// <3> 优先队列优化的djk求单源最短路,链式前向星存图 时间复杂度o(E * log(V))

typedef pair<int, int>pii;
const int N = (int) 1e5 + 11; // 最大点数
const int M = (int) 1e6 + 11;
const int INF = (int) 0x3f3f3f3f;

struct Edge{ // 边的定义
	int to, val, next;
	Edge(){}
	Edge(int _to, int _val, int _next){
		to = _to; val = _val; next = _next;
	}
}edge[M << 1]; // !!如果是双向图的话,边的数量是题目中描述的二倍

int n, m; // n 是图中的点数,m是图中的边数
int head[N], top;
void init(int n){ // 初始化
	memset(head, -1, sizeof(int) * (n + 1));
	top = 0;
}
void Add(int u, int v, int val){ // 加单向边
	edge[top] = Edge(v, val, head[u]);
	head[u] = top++;
}
void getmap(int m){ // 建图
	int u, v, val;
	while(m--){
		scanf("%d%d%d",&u , &v, &val); // u->v有边
		Add(u, v, val);
		Add(v, u, val); // 如果是双向图,加上这个代码
	}
}
int dis[N];
void djk(int st, int ed){ // 核心代码,点的序号可以从0-(n-1)或者1-n
	memset(dis, 0x3f, sizeof(int) * (n + 1));
	priority_queue<pii, vector<pii>, greater<pii> > que; // make_pair(dis, v) 表示v到起点的距离为dis
	dis[st] = 0; que.push(make_pair(0, st));
	while(!que.empty()){
		pii p = que.top(); que.pop(); // 每次取离起点最近的
		int v = p.second;
		if(dis[v] < p.first) continue; // 如果 没有更优,跳过
		for(int i = head[v]; ~i; i = edge[i].next){
			Edge e = edge[i];
			if(dis[e.to] > dis[v] + e.val) { // 是否可以进行松弛
				dis[e.to] = dis[v] + e.val;
				que.push(make_pair(dis[e.to], e.to));
			}
		}
	}
	printf("%d\n", dis[ed] == INF ? -1 : dis[ed]);
}
int main(){
	int t; scanf("%d%d", &t, &n);
	init(n);
	getmap(t);
	djk(n, 1);
	return 0;
}

 

 

第三种,spfa算法,实质上和上面的方法类似,做了一些优化。速度快,但是相比之下没有djk2稳定。

#include <stdio.h>
#include <queue>
#include <string.h>
using namespace std;

// <1> spfa求单源最短路,链式前向星存图 时间复杂度o(kE) k是常数,大多数情况下为2

const int N = (int) 1e5 + 11; // 最大点数
const int M = (int) 1e6 + 11; // 最大边数
const int INF = (int) 0x3f3f3f3f;

struct Edge{ // 边的定义
	int to, val, next;
	Edge(){}
	Edge(int _to, int _val, int _next){
		to = _to; val = _val; next = _next;
	}
}edge[M << 1]; // !!如果是双向图的话,边的数量是题目中描述的二倍

int n, m; // n 是图中的点数,m是图中的边数
int head[N], top;
void init(int n){ // 初始化
	memset(head, -1, sizeof(int) * (n + 1));
	top = 0;
}
void Add(int u, int v, int val){ // 加单向边
	edge[top] = Edge(v, val, head[u]);
	head[u] = top++;
}
void getmap(int m){ // 建图
	int u, v, val;
	while(m--){
		scanf("%d%d%d",&u , &v, &val); // u->v有边
		Add(u, v, val);
		Add(v, u, val); // 如果是双向图,加上这个代码
	}
}

bool vis[N]; int dis[N];
void spfa(int st, int ed){ // 核心代码,点序号从0-(n-1)或者1-n都可以
	memset(dis, 0x3f, sizeof(int) * (n + 1));
	memset(vis, false, sizeof(bool) * (n + 1)); // 清空和初始化
	queue<int>que; que.push(st);
	vis[st] = true; dis[st] = 0;
	while(!que.empty()){
		int u = que.front(); que.pop(); vis[u] = false;
		for(int i = head[u]; ~i; i = edge[i].next){
			Edge e = edge[i];
			if(dis[e.to] > dis[u] + e.val) {//是否可以进行松弛
				dis[e.to] = dis[u] + e.val;
				if(!vis[e.to]){ // 是否在队列中,如果在可以跳过
					que.push(e.to);
					vis[e.to] = true;
				}
			}

		}
	}
	printf("%d\n", dis[ed] == INF ? -1 : dis[ed]); // st到不了ed输出-1
}

int main(){
	int t; scanf("%d%d", &t, &n);
	init(n);
	getmap(t);
	spfa(n, 1);
	return 0;
}

 

 

第四种,floyd算法,应该是更加强大的算法,能求任意两个点之间的最短距离,在数据小的时候使用。(算法中有三重for循环,所以数据范围大会爆掉)

#include <stdio.h>
#include <queue>
#include <string.h>
using namespace std;

// <4> floyd求多源最短路,邻接矩阵存图 时间复杂度o(n^3),可求得任意两个点之间的最短距离

const int N = (int) 1000 + 11;
const int INF = (int) 0x3f3f3f3f;

int n, m;
int mp[N][N];
void init(int n){ // 初始化
	for(int i = 0; i < n; i++){
		for(int j = 0; j < n; j++){
			if(i == j) mp[i][j] = 0;
			else mp[i][j] = mp[j][i] = INF;
		}
	}
}
void getmap(int m){ // 建图
	int u, v, val;
	while(m--){
		scanf("%d%d%d", &u, &v, &val); u--; v--;
		mp[u][v] = mp[v][u] = min(mp[u][v], val); // 双向图写这行
		//mp[u][v] = min(mp[u][v], val); // 单向图写着行
	}
}
void floyd(int n){ // 核心代码,点的序号为0-n-1
	for(int k = 0; k < n; k++){
		for(int i = 0; i < n; i++){
			for(int j = 0; j < n; j++){
				mp[i][j] = min(mp[i][j], mp[i][k] + mp[k][j]); // dp
			}
		}
	}
}

int main(){
	int t; scanf("%d%d", &t, &n);
	init(n);
	getmap(t);
	floyd(n);
	printf("%d\n", mp[n - 1][0] == INF ? -1 : mp[n - 1][0]);
	return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值