「Day 5—最短路径」

最短路问题

单源最短路

全源最短路

Floyd算法

通过转移方程判断 i -> j 的路径中,是否有 i -> k -> j 更短,运用简单 dp 来转移状态。
f[i][j] 表示 i -> j 的最短路径长度。
但不要忘了初始化,一个点到其本身的最短路径为 1,即 f[i][i] = 1,其余的初始化为 '1e9' 即可。

for(int i = 1;i <= n;i ++){
	for(int j = 1;j <= n;j ++){
		if(i == j){
			f[i][j] = 0;
		}
		else f[i][j] = 1e9;
	}
}
for(int i = 1;i <= n;i ++){
	int u,v,w;
	cin >> u >> v >> w;
	f[u][v] = min(f[u][v],w);
	f[v][u] = min(f[v][u],w);
}
for(int k = 1;k <= n;k ++){
	for(int i = 1;i <= n;i ++){
		for(int j = 1;j <= n;j ++){
			if(f[i][j] > f[i][k] + f[k][j]){
				f[i][j] = f[i][k] + f[k][j];
			}
		}
	}
}

单源最短路

SPFA

我们需要先知道一个前置知识:松弛操作。
那么什么是松弛操作呢,就是当 d[to] > d[v] + e[i].len 的时候,更新 d[to]d[v] + e[i].len 即可。
那么 SPFA 的实现可以通过队列来实现,不断更新当前点所连的点,而每个点可能入队多次。
但 SPFA 容易被卡,所以一般是不用的,但是 SPFA 的最大作用是判负环,如果 cnt[v] > n 则说明其有负环。

#include<iostream>
#include<math.h>
#include<queue>
#include<vector>
#include<cstring>
using namespace std;

const int MAXN=1e4+5;
const long long INF=2147483647;
struct e {
	int v,w;
};
vector<e> G[MAXN];
queue<long long> q;
long long dis[MAXN],cnt[MAXN];
bool vis[MAXN];
long long n,m,s;

void SPFA() {
	for(int i=1;i<=n;i++){
		dis[i]=INF;
		vis[i]=0;
	}
	dis[s]=0,vis[s]=1;
	q.push(s);
	while(!q.empty()) {
		long long u=q.front();
		vis[u]=0;
		q.pop();
		for(auto edge:G[u]) {
			long long v=edge.v;
			long long w=edge.w;
			if(dis[v]>dis[u]+w) {
				dis[v]=dis[u]+w;
				cnt[v]=cnt[u]+1;
				if(cnt[v]>=n) return;
				if(!vis[v]) q.push(v);
				vis[v]=1;
			}
		}
	}
	return;
}

int main() {

	cin>>n>>m>>s;

	for(int i=1; i<=m; i++) {
		long long u,v,w;
		cin>>u>>v>>w;
		G[u].push_back({v,w});
	}

	SPFA();

	for(int i=1; i<=n; i++) {
		if(s==i) cout<<"0 ";
		else cout<<dis[i]<<" ";
	}

	return 0;
}

Dijkstra

其本质也是一种松弛操作,但其实现方式与 SPFA 有异同之处,首先在 SPFA 里面,一个点可能被入队多次,但在 Dijkstra 里面每个点只会访问一次。
那么其实现过程是什么呢,先初始化起点, d[s] = 0, 从一个集合中最短路长度最小的节点放入另一个集合中,对于这些刚放入的点的所有出边进行松弛操作。
但是这样的时间复杂度是 $ O(n^2) $,为了优化,有两种主流做法,第一是利用优先队列来维护最短路长度最小的节点,二是利用堆来维护。

堆优化
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
const int M = 5 * 1e5 + 5;
const int inf = 0x3f3f3f3f;
struct Edge {
    int to, next, len;
} e[M];
int h[N], tot = -1, n, m, s, d[N];
set<pair<int, int> > heap;
void addEdge(int x, int y, int len) {
    e[++tot] = {y, h[x], len};
    h[x] = tot;
}
void dijkstra(int s) {
    memset(d, 0x3f3f3f3f, sizeof(d));
    d[s] = 0;
    heap.insert({0,s});
    while(heap.size()){
        int v = heap.begin() -> second;
        heap.erase(heap.begin());
        for(int i = h[v];i != -1;i = e[i].next){
            int to = e[i].to;
            if(d[to] > d[v] + e[i].len){
                heap.erase({d[to],to});
                d[to] = d[v] + e[i].len;
                heap.insert({d[to],to});
            }
        }
    }
}
int main() {
    cin >> n >> m >> s;
    memset(h, -1, sizeof(h));
    for (int i = 0; i < m; i++) {
        int u, v, w;
        cin >> u >> v >> w;
        addEdge(u, v, w);
    }
    dijkstra(s);
    for (int i = 1; i <= n; i++) {
    	if(d[i] == 0x3f3f3f3f){
    		cout << pow(2,31) - 1 << " "; 
		}
        else cout << d[i] << " ";
    }
    return 0;
}
优先队列优化
#include<iostream>
#include<queue> 
#include<cstring>
#include<cmath>
using namespace std;

const int MAXN = 1e5 + 5;
const int MAXM = 5 * 1e5 +5;
const long long inf = pow(2,31) - 1;
int n,m,s;
struct node{
	int to,next,len;
}e[MAXM];
int h[MAXN];
int d[MAXN];
bool vis[MAXN];
int tot = 0;
void adge(int x,int y,int len){
	e[++ tot] = (node){y,h[x],len};
	h[x] = tot;
}
struct op{
	int w,now;
	bool operator <(const op &other)const{
		return w > other.w;
	} 
};
priority_queue<op> q;
void dijistra(){
	for(int i = 1;i <= n;i ++){
		d[i] = inf;
	}
	d[s] = 0;
	q.push({0,s});
	while(!q.empty()){
		int v = q.top().now;
		q.pop();
		if(vis[v]) continue;
		vis[v] = 1;
		for(int i = h[v];i;i = e[i].next){
			int c = e[i].to;
			if(d[c] > d[v] + e[i].len){
				d[c] = d[v] + e[i].len;
				q.push({d[c],c});
			}
		}
	}
}

int main(){
	
	cin >> n >> m >> s;
	for(int i = 1;i <= m;i ++){
		int u,v,w;
		cin >> u >> v >> w;
		adge(u,v,w);
	}
	dijistra();
	for(int i = 1;i <= n;i ++){
		cout << d[i] << " ";
	}
	return 0;
}

习题

P1339 [USACO09OCT] Heat Wave G

思路

水,就不说了,板子题,只需要注意数据范围和无向图即可

P1364 医院设置

思路

这个题其实还是一道很好的思维题,让你选一个点,求全图最短路,我们发现,这个题有点像树的重心,但是 \(n\) 的范围很小,没必要用,我们可以用 Floyd 算法,穷举每个点的可能性,找最小的那个。

代码
#include<iostream>
using namespace std;

int f[105][105];
int n;
int x[105];

int main(){
	cin >> n;
	for(int i = 1;i <= n;i ++){
		for(int j = 1;j <= n;j ++){
			if(i == j){
				f[i][j] = 0;
			}
			else f[i][j] = 1e9;
		}
	}
	for(int i = 1;i <= n;i ++){
		int u,v;
		cin >> x[i] >> u >> v;
		//这个题的输入比较魔幻,要注意一下
		if(u){
			f[i][u] = f[u][i] = 1;
		}
		if(v){
			f[i][v] = f[v][i] = 1;
		}
	}
	for(int k = 1;k <= n;k ++){
		for(int i = 1;i <= n;i ++){
			for(int j = 1;j <= n;j ++){
				if(f[i][j] > f[i][k] + f[k][j]){
					f[i][j] = f[i][k] + f[k][j];
				}
			}
		}
	}
	int minn = 1e9;
	for(int i = 1;i <= n;i ++){
		int ans = 0;
		for(int j = 1;j <= n;j ++){
			ans += x[j] * f[i][j];
		}
		minn = min(minn,ans);
	}
	cout << minn << "\n";
	return 0;
}

P1576 最小花费

思路

这个题看起来很模版,实际上也很模版,只是最短路的变种,原来是求最短,现在是乘积最大,利用优先队列和结构体重载运算符,维护最大值即可。

代码
#include<iostream>
#include<queue> 
#include<cstring>
#include<cmath>
using namespace std;

const int MAXN = 20505;
const int MAXM = 205005;
int n,m,s,t;
struct node{
	int to,next;
	double len;
}e[MAXM * 2];
int h[MAXN];
double d[MAXN];
bool vis[MAXN];
int tot = 0;
void adge(int x,int y,int len){
	e[++ tot] = (node){y,h[x],double(1.0-double(len * 0.01))};
	h[x] = tot;
}
struct op{
	double w;
	int now;
	bool operator <(const op &other)const{
		return w < other.w;
	} 
};
priority_queue<op> q;
void dijistra(){
	memset(d,0,sizeof(d));
	memset(vis,false,sizeof(vis));
	d[s] = 1.0;
	q.push({1.0,s});
	while(!q.empty()){
		int v = q.top().now;
		q.pop();
		if(vis[v]) continue;
		vis[v] = 1;
		for(int i = h[v];i;i = e[i].next){
			int c = e[i].to;
			if(d[c] < d[v] * e[i].len){
				d[c] = d[v] * e[i].len;
				q.push({d[c],c});
			}
		}
	}
}

int main(){
	
	cin >> n >> m;
	for(int i = 1;i <= m;i ++){
		int u,v,w;
		cin >> u >> v >> w;
		adge(u,v,w);
		adge(v,u,w);
	}
	cin >> s >> t;
	dijistra();
	printf("%.8lf",double((double(100))/double(d[t])));
	return 0;
}
posted @ 2024-08-11 17:54  To_Carpe_Diem  阅读(27)  评论(3编辑  收藏  举报