最短路
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。将源点入队,并重复以下步骤:
- 队首x出队
- 遍历所有以队首为起点的有向边(x,i),若dis[x]+w(x,i)<dis[i],则更新dis[i]
- 如果点i不在队列中,则i入队
- 若队列为空,跳出循环;否则执行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]);
}