1.单源最短路
1.1所有边权值都为正
1.1.1 朴素Dijkstra o(n^2)用于边数多
1.1.2 堆优化Dijkstra o(mlogn)用于点数多
1.2存在边权值为负
1.2.1 BellmanFord o(nm)
1.2.2 SPFA o(m)~o(nm)优化版的bellman
1.朴素dijkstra o(n^2)
注意坑:使用邻接矩阵,需要考虑两点间存在多条边的情况 ,取其中最短的一条
#include<bits/stdc++.h>
using namespace std;
const int N=505,M=1e5+5;
int g[N][N];
int n,m;
int dis[N];
bool vis[N];
int dijk(int st,int ed){
memset(dis,0x3f,sizeof dis);
memset(vis,false,sizeof vis);
dis[st]=0;
for(int i=1;i<=n-1;i++){
int t=-1;
for(int j=1;j<=n;j++){
if(!vis[j]&&(t==-1||dis[t]>dis[j])){
t=j;
}
}
vis[t]=true;
for(int j=1;j<=n;j++){
dis[j]=min(dis[j],dis[t]+g[t][j]);
}
}
return dis[ed];
}
int main(){
cin>>n>>m;
memset(g,0x3f,sizeof g);
while(m--){
int x,y,z;cin>>x>>y>>z;
//注意坑:使用邻接矩阵,需要考虑两点间存在多条边的情况
g[x][y]=min(g[x][y],z);
}
int res=dijk(1,n);
if(res!=0x3f3f3f3f) cout<<res;
else cout<<-1;
}
2.堆优化dijkstra o(mlogn)
#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> PII;
const int N=1.5*(1e5)+5,M=1.5*(1e5)+5;
int h[N],e[M<<1],ne[M<<1],w[M<<1],idx;
void add(int a,int b,int c){
e[idx]=b;
w[idx]=c;
ne[idx]=h[a];
h[a]=idx++;
}
int n,m;
int dis[N];
bool vis[N];
int dijk(int st,int ed){
memset(dis,0x3f,sizeof dis);
memset(vis,false,sizeof vis);
dis[st]=0;
priority_queue<PII,vector<PII>,greater<PII> >heap;
heap.push({0,st});
while(heap.size()){
PII t=heap.top();heap.pop();
int distance=t.first,ver=t.second;
if(vis[ver]) continue;
vis[ver]=true;
for(int i=h[ver];~i;i=ne[i]){
int j=e[i];
if(dis[j]>distance+w[i]){
dis[j]=distance+w[i];
heap.push({dis[j],j});
}
}
}
return dis[ed];
}
int main(){
cin>>n>>m;
memset(h,-1,sizeof h);
while(m--){
int x,y,z;cin>>x>>y>>z;
add(x,y,z);
}
int res=dijk(1,n);
if(res!=0x3f3f3f3f) cout<<res;
else cout<<-1;
}
3.BellmanFord o(nm) 限制最多k条边的最短路问题
注意每次迭代将dis[]拷贝到back[],用back[]对dis[]更新
更新的时候是取出每条边,用边的起点更新终点
#include<bits/stdc++.h>
using namespace std;
const int N=505,M=1e4+5,K=505;
int n,m,k;
struct edge{
int x,y,z;
}edges[M];
int dis[N];
bool vis[K];
int back[K];
int bellman(int st,int ed){
memset(dis,0x3f,sizeof dis);
memset(vis,false,sizeof vis);
dis[st]=0;
for(int i=1;i<=k;i++){//长度最大为k
memcpy(back,dis,sizeof dis);//dis->back 用back更新
for(int j=0;j<m;j++){
int x=edges[j].x,y=edges[j].y,z=edges[j].z;
dis[y]=min(dis[y],back[x]+z);
}
}
return dis[ed];
}
int main(){
cin>>n>>m>>k;
for(int i=0;i<m;i++){
int x,y,z;cin>>x>>y>>z;
edges[i]={x,y,z};
}
int res=bellman(1,n);
//存在负边
if(res>0x3f3f3f3f/2) cout<<"impossible";
else cout<<res;
}
4.SPFA o(m)~o(nm)
vis[]记录点是否在队列中
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5,M=1e5+5;
int h[N],e[M<<1],ne[M<<1],w[M<<1],idx;
void add(int a,int b,int c){
e[idx]=b;
w[idx]=c;
ne[idx]=h[a];
h[a]=idx++;
}
int n,m;
int dis[N];
queue<int> q;
bool vis[N];//是否在队列中
int spfa(int st,int ed){
memset(dis,0x3f,sizeof dis);
q.push(st);
dis[st]=0;
vis[st]=true;
while(!q.empty()){
int t=q.front();q.pop();
vis[t]=false;
for(int i=h[t];~i;i=ne[i]){
int j=e[i];
if(dis[j]>dis[t]+w[i]){
dis[j]=dis[t]+w[i];
if(!vis[j]){
q.push(j);
vis[j]=true;
}
}
}
}
return dis[ed];
}
int main(){
cin>>n>>m;
memset(h,-1,sizeof h);
while(m--){
int x,y,z;cin>>x>>y>>z;
add(x,y,z);
}
int res=spfa(1,n);
//存在负边
if(res>=0x3f3f3f3f/2){
cout<<"impossible"<<endl;
}else{
cout<<res<<endl;
}
}
拓展:SPFA判断负环问题
#include<bits/stdc++.h>
using namespace std;
const int N=2e3+5,M=1e4+5;
int h[N],e[M<<1],ne[M<<1],w[M<<1],idx;
void add(int a,int b,int c){
e[idx]=b;
w[idx]=c;
ne[idx]=h[a];
h[a]=idx++;
}
int n,m;
int dis[N];
queue<int> q;
bool vis[N];//是否在队列中
int cnt[N];//每个点的最短路包含的边的数量
int spfa_judge(){
memset(dis,0,sizeof dis);
for(int i=1;i<=n;i++){
vis[i]=true;
q.push(i);
}
while(!q.empty()){
int t=q.front();q.pop();
vis[t]=false;
for(int i=h[t];~i;i=ne[i]){
int j=e[i];
if(dis[j]>dis[t]+w[i]){
dis[j]=dis[t]+w[i];
cnt[j]=cnt[t]+1;
if(cnt[j]>=n) return true;//cnt>=n则存在环
if(!vis[j]){
q.push(j);
vis[j]=true;
}
}
}
}
return false;
}
int main(){
cin>>n>>m;
memset(h,-1,sizeof h);
while(m--){
int x,y,z;cin>>x>>y>>z;
add(x,y,z);
}
bool res=spfa_judge();
if(res){
cout<<"Yes"<<endl;
}else{
cout<<"No"<<endl;
}
}
信使
题意:所有点到点1距离最短距离的最大值,如果有不连通输出-1
提醒:
①注意点数和边数的数据范围,尤其是边数,如果为无向边要在题目范围的基础上*2 ②如果没给边的范围,在n个点的图上,最多有
条边,无向边则需要*2
int dis[N];
bool vis[N];
int dijkstra(){
memset(dis,0x3f,sizeof dis);
dis[1]=0;
priority_queue<PII,vector<PII>,greater<PII> >heap;
heap.push({dis[1],1});
while(heap.size()){
PII t=heap.top();heap.pop();
int distance=t.first,ver=t.second;
if(vis[ver]) continue;
vis[ver]=true;
for(int i=h[ver];~i;i=ne[i]){
int j=e[i];
if(dis[j]>distance+w[i]){
dis[j]=distance+w[i];
heap.push({dis[j],j});
}
}
}
int maxn=0;
for(int i=1;i<=n;i++){
if(dis[i]==0x3f3f3f3f)
return -1;
maxn=max(maxn,dis[i]);
}
return maxn;
}
int main(){
cin>>n>>m;
memset(h,-1,sizeof h);
for(int i=1;i<=m;i++){
int a,b,c;cin>>a>>b>>c;
add(a,b,c);add(b,a,c);
}
cout<<dijkstra();
}
最小花费
题意:两人之间转账需要扣除一定手续费,每两个人之间的手续费不同,问A向B转账,使B收到100元,求A至少转多少钱
思路:转化为从B到A的最短路问题,dis[B]初始化为100,路径长度为转账金额,假设手续费为z%,则路径转移时为distance/(1-z),求最短路
double dis[N];
bool vis[N];
double dijkstra(){
for(int i=1;i<=n;i++){
dis[i]=1e8;
}
dis[st]=100;
priority_queue<PDI,vector<PDI>,greater<PDI> >heap;
heap.push({dis[st],st});
while(heap.size()){
PDI t=heap.top();heap.pop();
double distance=t.first;
int ver=t.second;
if(vis[ver]) continue;
vis[ver]=true;
for(int i=h[ver];~i;i=ne[i]){
int j=e[i];
if(dis[j]>distance/(0.01*w[i])){
dis[j]=distance/(0.01*w[i]);
heap.push({dis[j],j});
}
}
}
return dis[ed];
}
int main(){
cin>>n>>m;
memset(h,-1,sizeof h);
for(int i=1;i<=m;i++){
int a,b,c;cin>>a>>b>>c;
c=100-c;
add(a,b,c);add(b,a,c);
}
cin>>ed>>st;
cout<<fixed<<setprecision(8)<<dijkstra();
}
最优乘车
题意:有m条路线,每条路线经过若干个站点,求从站点1到达站点n,所需的最少换乘次数
思路: 需要注意,换乘次数是指从一条路线转向另一条路线,如果到达同一条路线上的其他站点不算换乘。由于在同一条路线上的站点不算换乘次数,相当于一个站点到达同一路线后面的所有站点的花费都是相同的,所以我们可以将同一条路线上的,前面站点到后面所有站点距离设定为1,这样建图之后问题就转化为从1到n的最短路问题
注意: 由于每条路线最多建n*(n-1)/2条边,所以边的范围是m*n*(n-1)/2
int dijkstra(){
memset(dis,0x3f,sizeof dis);
dis[1]=0;
priority_queue<PII,vector<PII>,greater<PII> >heap;
heap.push({dis[1],1});
while(heap.size()){
PII t=heap.top();heap.pop();
int distance=t.first,ver=t.second;
if(vis[ver]) continue;
vis[ver]=true;
for(int i=h[ver];~i;i=ne[i]){
int j=e[i];
if(dis[j]>distance+w[i]){
dis[j]=distance+w[i];
heap.push({dis[j],j});
}
}
}
if(dis[n]==0x3f3f3f3f) return -1;
return dis[n]-1;
}
int main(){
cin>>m>>n;getchar();
memset(h,-1,sizeof h);
for(int i=1;i<=m;i++){
string s;getline(cin,s);
stringstream ss;ss<<s;
int t[N],pos=1;
while(ss>>t[pos]){
pos++;
}
//同一条路线上的,前面站点到后面所有站点距离为1
for(int i=1;i<pos;i++){
for(int j=i+1;j<pos;j++){
add(t[i],t[j],1);
}
}
}
int res=dijkstra();
if(res==-1) cout<<"NO";
else cout<<res;
}
昂贵的聘礼
题意:有n个物品,每个物品可以由其他物品搭配一些金币来兑换,也可以直接金币购买,所有兑换的物品所属的等级差值最大为m,求得到物品1的最小花费
思路:建立一个超级源点,连向所有物品,边权为直接购买的花费,表示直接购买。物品之间建立单向边,表示兑换的花费。枚举交易的等级区间,L[1]为物品1的等级,要想得到,所有交易的等级必须在
之间,枚举长度为m的区间,求超级源点0到物品1的最短路的最小值
//交易区间 [x,x+m]
int dijkstra(int x){
memset(dis,0x3f,sizeof dis);
memset(vis,false,sizeof vis);
dis[0]=0;
priority_queue<PII,vector<PII>,greater<PII> >heap;
heap.push({dis[0],0});
while(heap.size()){
PII t=heap.top();heap.pop();
int distance=t.first,ver=t.second;
if(vis[ver]) continue;
vis[ver]=true;
for(int i=h[ver];~i;i=ne[i]){
int j=e[i];
if(L[j]<x||L[j]>x+m) continue;
if(dis[j]>distance+w[i]){
dis[j]=distance+w[i];
heap.push({dis[j],j});
}
}
}
return dis[1];
}
int main(){
cin>>m>>n;
memset(h,-1,sizeof h);
for(int i=1;i<=n;i++){
int p,x;cin>>p>>L[i]>>x;
//虚拟远点到物品表示直接购买
add(0,i,p);
for(int j=1;j<=x;j++){
int t,v;cin>>t>>v;
add(t,i,v);
}
}
//虚拟源点的等级和终点相同
L[0]=L[1];
int res=0x3f3f3f3f;
//枚举交易的等级区间(L[1]-m,L[1])~(L[1],L[1]+m)
for(int i=L[1]-m;i<=L[1];i++){
res=min(res,dijkstra(i));
}
cout<<res;
}
新年好
题意:从点1开始,走过a,b,c,d,e五个点,顺序可变,求走的最小距离
思路:分别以点1以及a,b,c,d,e作为起点,求出这几个点到其他点的最短距离,然后枚举abcde的顺序,以1为最初起点,求相邻两点最短距离之和的最小值
int dis[6][N];
bool vis[N];
void dijkstra(int x){
if(!x) memset(dis,0x3f,sizeof dis);
int u=to[x];
memset(vis,false,sizeof vis);
dis[x][u]=0;
priority_queue<PII,vector<PII>,greater<PII> >heap;
heap.push({dis[x][u],u});
while(heap.size()){
PII t=heap.top();heap.pop();
int distance=t.first,ver=t.second;
if(vis[ver]) continue;
vis[ver]=true;
for(int i=h[ver];~i;i=ne[i]){
int j=e[i];
if(dis[x][j]>distance+w[i]){
dis[x][j]=distance+w[i];
heap.push({dis[x][j],j});
}
}
}
}
int main(){
cin>>n>>m;
for(int i=1;i<=5;i++) cin>>to[i];
memset(h,-1,sizeof h);
for(int i=1;i<=m;i++){
int a,b,c;cin>>a>>b>>c;
add(a,b,c);add(b,a,c);
}
to[0]=1;
sort(to+1,to+5+1);
for(int i=0;i<=5;i++){
mp[to[i]]=i;
}
for(int i=0;i<=5;i++){
dijkstra(i);
}
int res=0x3f3f3f3f;
do{
int now=0;
for(int i=0;i<5;i++){
now+=dis[mp[to[i]]][to[i+1]];
}
res=min(res,now);
}while(next_permutation(to+1,to+5+1));
cout<<res;
}
通信线路
题意:求1到n的路径中,不超过k条边的路径的最短长度
思路:二分答案,对于路径长度x,将路径中长度大于x的看作1,不大于x的看作0,此时图化为边权只有0和1,使用dijkstra求1到n的最短距离,这个距离就是路径中长度大于x的边数,如果不大于k,说明此时满足最多有k条边大于x,我们要求的就是满足在1到n的路径中第k短的路径的最小值,即x的最小值
bool dijkstra(int mid){
memset(dis,0x3f,sizeof dis);
memset(vis,false,sizeof vis);
dis[1]=0;
priority_queue<PII,vector<PII>,greater<PII> >heap;
heap.push({dis[1],1});
while(heap.size()){
PII t=heap.top();heap.pop();
int distance=t.first,ver=t.second;
if(vis[ver]) continue;
vis[ver]=true;
for(int i=h[ver];~i;i=ne[i]){
int j=e[i],x=0;
if(w[i]>mid) x=1;
if(dis[j]>dis[ver]+x){
dis[j]=dis[ver]+x;
heap.push({dis[j],j});
}
}
}
return dis[n]<=k;
}
int main(){
cin>>n>>m>>k;
memset(h,-1,sizeof h);
for(int i=1;i<=m;i++){
int a,b,c;cin>>a>>b>>c;
add(a,b,c);add(b,a,c);
}
int l=0,r=1e6+5;
while(l<r){
int mid=l+r>>1;
if(dijkstra(mid)){
r=mid;
}else{
l=mid+1;
}
}
if(r==1e6+5) cout<<-1;
else cout<<l;
}
道路与航线
题意:
思路:将图分成若干个连通块,连通块内都是道路,连通块之间是航线
#include<bits/stdc++.h>
using namespace std;
#define fast(); ios::sync_with_stdio(false);cin,tie(0),cout.tie(0);
typedef long long ll;
typedef pair<int,int> PII;
const int N=25100,M=150010;
int h[N],e[M],ne[M],w[M],idx;
void add(int a,int b,int c){
e[idx]=b;
w[idx]=c;
ne[idx]=h[a];
h[a]=idx++;
}
int t,r,p,s;
int id[N];
vector<int> block[N];
int d[N];
int cnt;
void dfs(int u){
id[u]=cnt;
block[cnt].push_back(u);
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
if(!id[j]) dfs(j);
}
}
int dis[N];
queue<int> q;
bool vis[N];
void dijkstra(int u){
priority_queue<PII, vector<PII>, greater<PII>> heap;
for(auto t:block[u]){
heap.push({dis[t],t});
}
while(heap.size()){
PII t = heap.top();heap.pop();
int ver=t.second,distance=t.first;
if(vis[ver]) continue;
vis[ver]=true;
for(int i=h[ver];~i;i=ne[i]){
int j=e[i];
if(dis[j]>distance+w[i]){
dis[j]=distance+w[i];
if(id[j]==u){
heap.push({dis[j],j});
}
}
d[id[j]]--;
if(d[id[j]]==0&&id[j]!=u){
q.push(id[j]);
}
}
}
}
void topSort(){
memset(dis,0x3f,sizeof dis);
dis[s]=0;
for(int i=1;i<=cnt;i++){
if(!d[i]){
q.push(i);
}
}
while(!q.empty()){
int t=q.front();q.pop();
dijkstra(t);
}
}
int main(){
cin>>t>>r>>p>>s;
memset(h,-1,sizeof h);
for(int i=1;i<=r;i++){
int a,b,c;cin>>a>>b>>c;
add(a,b,c);add(b,a,c);
}
//建立所有连通块
for(int i=1;i<=t;i++){
if(!id[i]){
++cnt;
dfs(i);
}
}
//连通块之间建边
for(int i=1;i<=p;i++){
int a,b,c;cin>>a>>b>>c;
add(a,b,c);
d[id[b]]++;
}
//连通块之间拓扑排序,按照拓扑排序的顺序进行dijkstra
topSort();
for(int i = 1; i <= t; i++)
{
if(dis[i] > 0x3f3f3f3f / 2) puts("NO PATH");
else printf("%d\n", dis[i]);
}
}
最优贸易
题意:在每一点可以买入或卖出物品,物品价格在不同点不一样,只能交易一次,问从1到n最多能赚多少钱
思路:
由于走过一个点后,不能保证以后不会再更新,所以不能使用dijkstra,而是使用SPFA
正反建图,正序跑起点到终点的最小值,逆序跑终点到起点的最大值,枚举中间点
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5,M=2e6+5;
typedef pair<int,int> PII;
int n,m;
int h[N],hr[N],e[M],ne[M],idx;
int w[N];
void add(int h[],int a,int b){
e[idx]=b;
ne[idx]=h[a];
h[a]=idx++;
}
int dmax[N],dmin[N];
//以i为分界点,在1~i买入的最小值,在i~n卖出的最大值
bool vis[N];
void spfa(int h[],int dis[],int u,bool f){
if(f) memset(dis,0x3f,sizeof dmin);//注意标明size,否则只会memset一个地址
dis[u]=w[u];
memset(vis,false,sizeof vis);
queue<int> q;
q.push(u);
vis[u]=true;
while(!q.empty()){
int t=q.front();q.pop();
vis[t]=false;
for(int i=h[t];~i;i=ne[i]){
int j=e[i];
if(f){//最小值
if(dis[j]>min(dis[t],w[j])){
dis[j]=min(dis[t],w[j]);
if(!vis[j]){
q.push(j);
vis[j]=true;
}
}
}else{//最大值
if(dis[j]<max(dis[t],w[j])){
dis[j]=max(dis[t],w[j]);
if(!vis[j]){
q.push(j);
vis[j]=true;
}
}
}
}
}
}
int main(){
cin>>n>>m;
memset(h,-1,sizeof h);
memset(hr,-1,sizeof hr);
for(int i=1;i<=n;i++){
cin>>w[i];
}
for(int i=1;i<=m;i++){
int x,y,c;cin>>x>>y>>c;
if(c==1){
add(h,x,y);add(hr,y,x);
}else{
add(h,x,y);add(h,y,x);
add(hr,x,y);add(hr,y,x);
}
}
spfa(h,dmin,1,true);
spfa(hr,dmax,n,false);
int res=0;
for(int i=1;i<=n;i++){
res=max(res,dmax[i]-dmin[i]);
}
cout<<res;
}
选择最佳线路
题意:可以选择多个起点到终点,求最短距离
思路:① 建立虚拟原点,和所有起点连长度为0的边,然后求最短路
② 反向建图,从终点开始求最短路,对比几个起点到终点的距离
最短路计数
if(dist[j]>dist[t]+1){
dist[j]=dist[t]+1;
cnt[j]=cnt[t];
q.push(j);
}
else if(dist[j]==dist[t]+1){
cnt[j]+=cnt[t];
}