最短路算法

最短路算法

存在最短路的前提

不存在负环。

链接

还是 OIWiki 好啊~~

Floyd 算法

两两间最短路,可判负环。时间复杂度 O(n3)O(n^3)

像 DP 的思路一样。

fk,x,yf_{k,x,y} 表示允许经过 1k1\sim k 的点,xyx\to y 的最短距离。

O(n3)O(n^3) 的 DP 即可。

按照 k,x,yk,x,y 的顺序循环,每次与 (x,k),(k,y)(x,k),(k,y)min\min 即可。

f[k][x][y] = min(f[k - 1][x][y], f[k - 1][x][k] + f[k - 1][k][y]);

因为第一维对结果无影响,我们可以发现数组的第一维是可以省略的,于是可以直接改成 f[x][y] = min(f[x][y], f[x][k]+f[k][y])

搬 OIWiki 的代码:

for (k = 1; k <= n; k++) {
for (x = 1; x <= n; x++) {
for (y = 1; y <= n; y++) {
f[k][x][y] = min(f[k - 1][x][y], f[k - 1][x][k] + f[k - 1][k][y]);
}
}
}

综上时间复杂度就是 O(n3)O(n^3),空间复杂度就是 O(n2)O(n^2)

Floyd 求图的传递闭包

参考博客

用 bitset 优化可以实现 O(n3w)O(\frac{n^3}{w}) 的复杂度。

Floyd 判负环

x,fx,x<0存在负环\exists x,f_{x,x}<0 \Rightarrow \text{存在负环}

Bellman-Ford 算法

单源最短路,可判负环。时间复杂度 O(nm)O(nm)

共循环 O(n)O(n) 次,每次枚举每一条边进行松弛操作(即对边 (u,v)(u,v)disvdis_v 进行取 min\min 操作)。若在某一次没有对任意一条边进行松弛操作,可以直接结束循环。

证明只用循环 n1n-1 次即可求出最短路。

因为第 ii 次操作可以确定最短路经过 ii 条边的结点的 disdis,因此最坏情况下只需要重复 n1n-1 次即可。

证毕。

然鹅我们一般循环 nn 次,为的是顺便判一下有没有负环(最短路是否存在)。

判负环

若第 nn 次仍然可以进行松弛操作,则存在负环。

Code (显然是从 OIWiki 搬的)

struct Edge {
int u, v, w;
};
vector<Edge> edge;
int dis[MAXN], u, v, w;
const int INF = 0x3f3f3f3f;
bool bellmanford(int n, int s) {
memset(dis, 0x3f, sizeof(dis));
dis[s] = 0;
bool flag = false; // 判断一轮循环过程中是否发生松弛操作
for (int i = 1; i <= n; i++) {
flag = false;
for (int j = 0; j < edge.size(); j++) {
u = edge[j].u, v = edge[j].v, w = edge[j].w;
if (dis[u] == INF) continue;
// 无穷大与常数加减仍然为无穷大
// 因此最短路长度为 INF 的点引出的边不可能发生松弛操作
if (dis[v] > dis[u] + w) {
dis[v] = dis[u] + w;
flag = true;
}
}
// 没有可以松弛的边时就停止算法
if (!flag) {
break;
}
}
// 第 n 轮循环仍然可以松弛时说明 s 点可以抵达一个负环
return flag;
}

SPFA 算法

最坏时间复杂度仍为 O(nm)O(nm)

很显然,只有上一次被松弛的结点,所连接的边,才有可能引起下一次的松弛操作。

那么我们用队列来维护「哪些结点可能会引起松弛操作」,就能只访问必要的边了。

SPFA 也可以用于判断 ss 点是否能抵达一个负环,只需记录最短路经过了多少条边,当经过了至少(\geqnn 条边时,说明 ss 点可以抵达一个负环。

struct edge {
int v, w;
};
vector<edge> e[maxn];
int dis[maxn], cnt[maxn], vis[maxn];
queue<int> q;
bool spfa(int n, int s) {
memset(dis, 63, sizeof(dis));
dis[s] = 0, vis[s] = 1;
q.push(s);
while (!q.empty()) {
int u = q.front();
q.pop(), vis[u] = 0;
for (auto ed : e[u]) {
int v = ed.v, w = ed.w;
if (dis[v] > dis[u] + w) {
dis[v] = dis[u] + w;
cnt[v] = cnt[u] + 1; // 记录最短路经过的边数
if (cnt[v] >= n) return false;
// 在不经过负环的情况下,最短路至多经过 n - 1 条边
// 因此如果经过了多于 n 条边,一定说明经过了负环
if (!vis[v]) q.push(v), vis[v] = 1;
}
}
}
return true;
}

Dijkstra 算法

单源最短路。时间复杂度 O(n2)O(n^2)O(mlogm)O(m \log m)。必须为非负权图。

搬 OIWiki 甚至懒得打字

简单来说,就是每次选一条集合内目前最短的路径,然后扩展一圈,放回集合。

时间复杂度 O(n2)O(n^2) 的。

Code(搬的)

struct edge {
int v, w;
};
vector<edge> e[maxn];
int dis[maxn], vis[maxn];
void dijkstra(int n, int s) {
memset(dis, 63, sizeof(dis));
dis[s] = 0;
for (int i = 1; i <= n; i++) {
int u = 0, mind = 0x3f3f3f3f;
for (int j = 1; j <= n; j++)
if (!vis[j] && dis[j] < mind) u = j, mind = dis[j];
vis[u] = true;
for (auto ed : e[u]) {
int v = ed.v, w = ed.w;
if (dis[v] > dis[u] + w) dis[v] = dis[u] + w;
}
}
}

优先队列优化

时间复杂度 O(mlogm)O(m\log m)

复杂度易证,略。

终于有我自己写的代码了

P4779 【模板】单源最短路径(标准版)

#include<bits/stdc++.h>
#define ll long long
#define pf printf
#define sf scanf
using namespace std;
const int N=1e5+7;
int cnt,head[N];
struct edge{
int to,ne,val;
} e[N<<1];
void add(int u,int v,int w) { e[++cnt]={v,head[u],w},head[u]=cnt; }
int n,m,s,u,v;
int w;
struct node{
int u,dis;
bool operator>(const node &x)const{
return dis>x.dis;
}
};
priority_queue<node,vector<node>,greater<node> > q;
int dis[N];
bool vi[N];
void getans(){
memset(dis,0x3f,sizeof(dis));
dis[s]=0;
q.push({s,0});
while(!q.empty()){
int u=q.top().u,d=q.top().dis;
q.pop();
if(vi[u]) continue;
vi[u]=1;
for(int i=head[u];i;i=e[i].ne){
int v=e[i].to,w=e[i].val;
if(d+w<dis[v]){
dis[v]=d+w;
q.push({v,dis[v]});
}
}
}
}
int main(){
sf("%d%d%d",&n,&m,&s);
for(int i=1;i<=m;i++){
sf("%d%d%d",&u,&v,&w);
add(u,v,w);
}
getans();
for(int i=1;i<=n;i++){
pf("%d ",dis[i]);
}
}

Johnson 全源最短路径算法

……到时候补充。

差分约束系统

其实这才是我今天的正题额。

OIWiki参考博客另一篇博客

若对这样一组不等式,我们要求 max(xixj)max(x_i-x_j),那么:

对于 xvxubkx_v-x_u\leq b_k 我们建一条有向边 (u,v,bk)(u,v,b_k),在图上跑单源最短路 SPFA 算法(当然这个例子没有负权,也可以跑 Dijkstra 算法),ans=disidisjans=dis_i-dis_j

为什么呢?

可以发现,在求最短路的过程中,对于边 (u,v,w)(u,v,w) 总有 disvdisu+wdis_v\leq dis_u+w,我们把它叫做三角不等式。即:

disvdisywdis_v-dis_y\leq w

这个式子和

xjxibkx_j-x_i\leq b_k

是不是很像呢?

因此差分约束系统可以转换成有向图求解。

因为我们的有向图不一定连通,为了方便,我们一般设一个超级源点 ss,令 diss=Ddis_s=D,连边 sx,1xns\to x,1\leq x \leq n,边权为 00,即增加关系 xs0,1xnx-s\leq 0,1\le x \le n。这样,我们就可以求出一组解 {x1,x2,,xn}D\{ x_1,x_2,\dots,x_n\} \le D(毕竟 ss 与所有点都有权为 00 的边,所以 disxdis_x 最大也是 DD)。

当然,如果差分约束系统里的所有 \le 换成 \geq 也可以做,只是把求最短路变成求最长路罢了。

SPFA 最短路 Code

P5960 【模板】差分约束

#include<bits/stdc++.h>
#define ll long long
#define pf printf
#define sf scanf
using namespace std;
const int N=5e3+7;
int n,m,u,v,w;
int cnt,head[N];
struct edge{
int to,ne,val;
}e[N<<1];
int s;
void add(int u,int v,int w){
e[++cnt]={v,head[u],w};
head[u]=cnt;
}
int dis[N];
bool vi[N];
int tot[N];
bool SPFA(){
queue<int> q;
q.push(s);vi[s]=1;
memset(dis,0x3f,sizeof(dis));
dis[s]=0;
while(!q.empty()){
int u=q.front();
q.pop();vi[u]=0;
for(int i=head[u];i;i=e[i].ne){
int v=e[i].to,w=e[i].val;
if(dis[u]+w<dis[v]){
dis[v]=dis[u]+w;
tot[v]=tot[u]+1;
if(tot[v]>n){//注意因为加入了超级源点,因此一共有 n+1 个点,最短路包含恰好 n 条边是可以的。
return 0;
}
if(!vi[v]) q.push(v);
vi[v]=1;
}
}
}
return 1;
}
int main(){
sf("%d%d",&n,&m);
s=n+1;
for(int i=1;i<=n;i++) add(s,i,0);
for(int i=1;i<=m;i++){
sf("%d%d%d",&v,&u,&w);
add(u,v,w);
}
if(SPFA())
for(int i=1;i<=n;i++){
pf("%d ",dis[i]);
}
else pf("NO\n");
}

同余最短路

简介

来自 OI Wiki,讲得好,就搬上来了。

给定 nn 个整数,每个数字可以使用多次,判定能拼出哪些数字。

其中给定数字的值域 VV 使得时间复杂度主要是 O(nV)O(nV) 的。

如果我们只有一个数字 aa,那么我们可以拼出 0a,1a,2a,,na0a,1a,2a,\cdots, na

如果再来一个 bb,我们就可以拼出 pa+qbpa+qb

我们将 aa 视为同余系的模数,认为只要有 qbx(moda)qb \equiv x(\bmod a) 我们就可以拼出 x+pax+pa 了。因此我们需要求出只使用 bb,在模 aa 意义下可以到达那些 xx

对于每个 x(moda)x(\bmod a),我们连一条 x(x+b)modax \to (x+b) \bmod a 的边。边权为 bb。对于除了 aan1n-1 个数皆是如此。

这样我们从 x=0(moda)x=0 (\bmod a) 开始跑最短路。disxdis_x 就表示我可以走到的最小的 moda=x\bmod a = x 的数字是什么,此后的 disx+padis_x + pa 都是可以走到的。

经验

模板题:P3403 跳楼机

hh 减一,当做从第零层开始会更加方便一些。

xx 为模数,求该同余系意义下的同余最短路。时间复杂度 O(xlogx)O(x \log x)

P2371 [国家集训队] 墨墨的等式

同样很板子。有 n12n \le 12 个数字,时间复杂度 O(nVlog(nV))O(nV \log (nV))

posted @   wing_heart  阅读(5)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示