「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;
}
本文来自一名初中牲,作者:To_Carpe_Diem