最短路问题

最短路

Floyd多源最短路

基本用法

for(int i=1;i<=p;i++) 
    for(int j=1;j<=p;j++) dis[i][j]=INF;
for(int k=1;k<=p;k++)
    for(int i=1;i<=p;i++)
        for(int j=1;j<=p;j++)
            dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);

优化版本

void Floyd_1(){//只适用于无向图的情况,距离矩阵必然是对称的
    for(int k = 1; k <= N; k++){
        for(int i = 1; i <= N; i++){
            int t = A[i][k];
            for(int j = 1; j <= i; j++){
                A[i][j] = min(A[i][j], t+A[k][j]);
                A[j][i] = A[i][j];
            }
        }
    }
}

求可达性矩阵

for(int k=1;k<=n;k++)//dis初始化为0,有边为1
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            dis[i][j]=dis[i][j]|(dis[i][k]&dis[k][j]);

dijkstra单源最短路

维护两个集合S和T,S是已经确定最短路的点,T是剩余点,建立最短路数组lowcost[],先将起始点的lowcost设为0,其余点设为INF,每次从lowcost[]中选择最小的加入S,再用这个点去更新其它点的lowcost[],循环n次就可以得到所有点的最短路。

普通版(适用于稠密图)

#include <bits/stdc++.h>
using namespace std;
const int maxn=105;
const int INF=1e9;
int dis[maxn][maxn];
int lowcost[maxn];
int vis[maxn];
void dijkstra(int start,int n){
    fill(lowcost+1,lowcost+1+n,INF);
    fill(vis+1,vis+1+n,0);
    lowcost[start]=0;
    for(int i=1;i<=n;i++){
        int minp=-1,minn=INF;
        for(int j=1;j<=n;j++){
            if(!vis[j]&&lowcost[j]<minn){
                minp=j;
                minn=lowcost[j];
            }
        }
        if(minp==-1)
            break;
        vis[minp]=1;
        for(int j=1;j<=n;j++){
            if(lowcost[minp]+dis[minp][j]<lowcost[j]){
                lowcost[j]=lowcost[minp]+dis[minp][j];
            }
        }
    }
}
int main () {
    int n,m,s;
    while(scanf("%d%d",&n,&m)){
        if(n==0 && m==0) return 0;
        s=1;
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                if(i!=j)
                    dis[i][j]=INF;
            }
        }
        for(int i=1;i<=m;i++){
            int u,v,w;
            scanf("%d%d%d",&u,&v,&w);
            dis[v][u]=dis[u][v]=min(dis[u][v],w);
        }
        dijkstra(s,n);
        printf("%d
",lowcost[n]);
    }
}

邻接表+堆优化版(适用于稀疏图)

同邻接矩阵的区别在于,每次有新的点加入集合后,不需要遍历所有的点,而是将相邻的点存入优先队列,队列中队首的点永远是距离源点最近的点,直接拿出判断是否未加入集合即可。分析得每条边都要插入一次点,插入的复杂度为O(logE),因此整体复杂度为(O(E * logE))

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll INF=1e18;
const int maxn=1e5+5;
const int maxm=2e5+5;
struct node{
	int id;ll cost;
	bool operator<(const node &b)const{
		return cost>b.cost;//将小的放上面
	}
};
struct edge{
	int v;ll cost;
};
vector<edge>E[maxm];
ll lowcost[maxn];
bool vis[maxn];
void dijkstra(int n,int start){
	fill(vis+1,vis+1+n,0);
	fill(lowcost+1,lowcost+1+n,INF);
	priority_queue<node>q;
	lowcost[start]=0; q.push({start,0});
	while(!q.empty() ){
		int u=q.top().id;
		q.pop();
		if(vis[u])continue;
		vis[u]=1;
		for(int i=0;i<E[u].size();i++){
			int v=E[u][i].v;
			ll cost=E[u][i].cost;
			if(!vis[v]&&lowcost[v]>lowcost[u]+cost){
				lowcost[v]=lowcost[u]+cost;
				q.push({v,lowcost[v]});
			}
		}
	}
}
void addedge(int u,int v,int w){
	E[u].push_back({v,w});
}
int main(){
	int n,m,s;
	scanf("%d%d%d",&n,&m,&s);
	for(int i=1;i<=m;i++){
        int u,v;ll w;
		scanf("%d%d%lld",&u,&v,&w);
		addedge(u,v,w);
	}
	dijkstra(n,s);
	for(int i=1;i<=n;i++)
		printf("%lld ",lowcost[i]);
}

Bellman_Ford 单源负权边最短路

以下操作循环执行至多 n-1 次, n 为顶点数:

  • 对于每一条边E[u,v,w]进行松弛,如果dis[v]>dis[u]+w,更新dis[v]。如果在一个循环中没有一个顶点被更新,那么所有顶点的最短路已经更新完毕。

在n-1次之后再进行一次所有边的松弛,如果有发生更新,说明存在负权边

复杂度为(V*E)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll INF=1e17;
const int maxn=1e5+5;
const int maxm=1e6+5;
ll dis[maxn],tot=0;
struct edge{
	ll u,v,w;
}E[maxm];
void addedge(int u,int v,int w){
	E[++tot].u=u;
	E[tot].v=v;
	E[tot].w=w;
}
int n,m;
int bellman_ford(int n,int s){
	fill(dis+1,dis+1+n,INF);
	dis[s]=0;
	for(int i=1;i<=n-1;i++){//最多n-1次
		int flag=0;
		for(int j=1;j<=tot;j++)
			if(dis[E[j].v]>dis[E[j].u]+E[j].w){
				flag=1;
				dis[E[j].v]=dis[E[j].u]+E[j].w;
			}
		if(!flag) return 1;//未发生松弛,已经更新完毕
	}
	for(int j=1;j<=tot;j++){
		if(dis[E[j].v]>dis[E[j].u]+E[j].w)
			return 0;
	}
	return 1;
}
int main(){
    int n,m,s;
    cin>>n>>m>>s;
    for(int i=1;i<=m;i++){
        ll u,v,w;
        scanf("%lld%lld%lld",&u,&v,&w);
        addedge(u,v,w);
    }
    bellman_ford(n,s);
    for(int i=1;i<=n;i++){
        printf("%lld ",dis[i]!=INF?dis[i]:2147483647);
    }
}

SFPA 单源负权边最短路

用dis数组记录源点到有向图上任意一点距离,其中源点到自身距离为0,到其他点距离为INF。将源点入队,并重复以下步骤:

  1. 队首x出队
  2. 遍历所有以队首为起点的有向边(x,i),若dis[x]+w(x,i)<dis[i],则更新dis[i]
  3. 如果点i不在队列中,则i入队
  4. 若队列为空,跳出循环;否则执行1

平均复杂度不确定,极限复杂度为O(VE),但是大多数时候比bellman_ford快

SLF优化:进队操作:进之前判一下和队首的dis[],若小于队头就直接插在队头。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e4+5;
const int maxm=1e6+5;
const int INF=2e9;
struct edge{
    int v,w,next;
}E[maxm];
int tot=0,head[maxn];
void addedge(int u,int v,int w){
    E[++tot].v=v;
    E[tot].w=w;
    E[tot].next=head[u];
    head[u]=tot;
}
int dis[maxn],vis[maxn];
int q[maxn];
int n,m,s;
void spfa(){
    fill(dis,dis+n+1,INF);
    fill(vis,vis+n+1,0);
    int l=0,r=1;
    q[1]=s;
    dis[s]=0;
    vis[s]=1;
    while(l!=r){
        int u=q[l=l==n?0:l+1];
        vis[u]=0;
        for(int i=head[u];i;i=E[i].next){
            int v=E[i].v;
            int w=E[i].w;
            if(dis[u]+w<dis[v]){
                dis[v]=dis[u]+w;
                if(!vis[v]){
                    if(dis[v]>dis[l+1])q[r=r==n?0:r+1]=v;
                    else q[l]=v,l=l==0?n:l-1;
                    vis[v]=1;
                }
            }
        }
    }
}
int main(){
    cin>>n>>m>>s;
    for(int i=1;i<=m;i++){
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        addedge(u,v,w);
    }
    spfa();
    for(int i=1;i<=n;i++){
        printf("%d ",dis[i]!=INF?dis[i]:2147483647);
    }
}

例题

Cow Contest(Floyd)

https://www.luogu.org/problem/P2419

题意:N个人,能力各不相同,两两比赛能力强的获胜,进行了M场比赛,给出比赛结果,问知道确切排名的人有几个?

思路:一个人若能与其他所有人的关系都确定,那么他的排名也是确定的。将能力关系转化为有向图,A战胜了B,就建立一条A指向B的边。如何判断两个人关系是否确定?只要从任意一方到另一方是可达的,那么他们的关系就是确定的。利用floyd求可达性矩阵,然后(n^2)枚举即可得出答案

#include <iostream>
using namespace std;
const int maxn=505;
int dis[maxn][maxn];
int a[maxn];
int main(){
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		int u,v;
		cin>>u>>v;
		dis[u][v]=1;
	}
	for(int k=1;k<=n;k++)
		for(int i=1;i<=n;i++)
			for(int j=1;j<=n;j++)
				dis[i][j]=dis[i][j]|(dis[i][k]&dis[k][j]);
	int ans=0;
	for(int i=1;i<=n;i++){
		int ok=1;
		for(int j=1;j<=n;j++){
			if(i==j)continue;
			if(!dis[i][j]&&!dis[j][i]){
				ok=0;
				break;
			}
		}
		if(ok)ans++;
	}
	cout<<ans<<endl;
}

灾后重建(Floyd)

https://www.luogu.org/problem/P1119

题意:N个点,M条边(带权),每个点i在t[i]时间时才会加入图中,多个询问,问t时刻u,v两点的最短路

思路:按照Floyd的方法,将点与询问都按时间从小到大排序,通过当前询问时间点前已存在(新加入)的点逐个进行松弛操作

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=205;
const int maxq=5e4+5;
const int INF=0x3f3f3f3f;
int dis[maxn][maxn];
struct node{
	int id,t;
}a[maxn];
struct Q{
	int u,v,t,id;
}query[maxq];
int ans[maxq];
bool cmp1(node a,node b){
	return a.t<b.t;
}
bool cmp2(Q a,Q b){
	return  a.t<b.t;
}
int ok[maxn];
int main(){
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			dis[i][j]=INF;
	for(int i=1;i<=n;i++){
		cin>>a[i].t;
		a[i].id=i;
	}
	for(int i=1;i<=m;i++){
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		u++;v++;
		dis[u][v]=dis[v][u]=w;
	}
	sort(a+1,a+1+n,cmp1);
	int q;
	cin>>q;
	for(int i=1;i<=q;i++){
		cin>>query[i].u>>query[i].v>>query[i].t;
		query[i].u++;query[i].v++;
		query[i].id=i;
	}
	sort(query+1,query+1+q,cmp2);
	int p=1;
	for(int i=1;i<=q;i++){
		while(p<=n && query[i].t>=a[p].t){
			int v=a[p].id;
			ok[v]=1;
			for(int j=1;j<=n;j++)
				for(int k=1;k<=n;k++)
					dis[j][k]=min(dis[j][k],dis[j][v]+dis[v][k]);
			p++;
		}
		int &u=query[i].u,&v=query[i].v,&id=query[i].id;
		if(ok[u] && ok[v] && dis[u][v]!=INF)
			ans[id]=dis[u][v];
		else ans[id]=-1;
	}
	for(int i=1;i<=q;i++)
		printf("%d
",ans[i]);
}

负环(SPFA)

https://www.luogu.org/problem/P3385

给定一个有向图,判断是否存在从1能到达额负环

可以用bellman_ford解,将与1连通的点和边重现建图,跑一遍bellman_ford即可(有超时风险)。

也可以用spfa从1开始跑,记录每个点的松弛次数,如果>=n,则存在负环

#include <bits/stdc++.h>
using namespace std;
const int maxn=1e4+5;;
const int maxm=1e6+5;
const int INF=2e9;
struct edge {
    int v,w,next;
} E[maxm];
int tot=0,head[maxn];
void addedge(int u,int v,int w) {
    E[++tot].v=v;
    E[tot].w=w;
    E[tot].next=head[u];
    head[u]=tot;
}
int dis[maxn];
int vis[maxn],cnt[maxn];
int t,n,m;
bool spfa(int s,int n) {
    fill(dis,dis+n+1,INF);
    fill(vis,vis+n+1,0);
    fill(cnt,cnt+n+1,0);
    queue<int>q;
    q.push(s);
    vis[s]=1;
    dis[s]=0;
    while(!q.empty()) {
        int u=q.front();
        q.pop();
        vis[u]=0;
        if(cnt[u]>=n)
            return 1;
        for(int i=head[u]; i; i=E[i].next) {
            int v=E[i].v,w=E[i].w;
            if(dis[u]+w<dis[v]) {
                dis[v]=dis[u]+w;
                if(!vis[v]) {
                    q.push(v);
                    vis[v]=1;
                    cnt[v]++;
                    if(cnt[v]>=n)
                        return 1;
                }
            }
        }
    }
    return 0;
}
int main() {
    cin>>t;
    while(t--) {
        cin>>n>>m;
        memset(head,0,sizeof(head));
        tot=0;
        for(int i=1; i<=m; i++) {
            int u,v,w;
            scanf("%d%d%d",&u,&v,&w);
            addedge(u,v,w);
            if(w>=0)
                addedge(v,u,w);
        }
        if(spfa(1,n)) {
            printf("YE5
");
        } else {
            printf("N0
");
        }
    }
}

P1144 最短路计数(dijkstra)

给定一个无向图,边权均为1,问每个点到其他点的最短路条数,答案对100003取模

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll INF=1e18;
const int mod=100003;
const int maxn=1e6+5;
const int maxm=2e6+5;
struct node{
	int id;ll cost;
	bool operator<(const node &b)const{
		return cost>b.cost;//将小的放上面
	}
};
struct edge{
	int v;ll cost;
};
vector<edge>E[maxm];
ll lowcost[maxn];
int cnt[maxn];
bool vis[maxn];
void dijkstra(int n,int start){
	fill(vis+1,vis+1+n,0);
	fill(lowcost+1,lowcost+1+n,INF);
	priority_queue<node>q;
	lowcost[start]=0; q.push({start,0});
	cnt[start]=1;
	while(!q.empty() ){
		int u=q.top().id;
		q.pop();
		if(vis[u])continue;
		vis[u]=1;
		for(int i=0;i<E[u].size();i++){
			int v=E[u][i].v;
			ll cost=E[u][i].cost;
			if(!vis[v]){
                if(lowcost[v]>lowcost[u]+cost){
                    lowcost[v]=lowcost[u]+cost;
                    q.push({v,lowcost[v]});
                    cnt[v]=cnt[u];
                }
                else if(lowcost[v]==lowcost[u]+cost){
                    cnt[v]=(cnt[v]+cnt[u])%mod;
                }
			}
		}
	}
}
void addedge(int u,int v,int w){
	E[u].push_back({v,w});
}
int main(){
	int n,m,s;
	scanf("%d%d",&n,&m);
	s=1;
	for(int i=1;i<=m;i++){
        int u,v;
		scanf("%d%d",&u,&v);
		addedge(u,v,1);
		addedge(v,u,1);
	}
	dijkstra(n,s);
	for(int i=1;i<=n;i++)
		printf("%d
",cnt[i]);
}
原文地址:https://www.cnblogs.com/ucprer/p/11389722.html