单源最短路

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)

849. Dijkstra求最短路 I - AcWing

注意坑:使用邻接矩阵,需要考虑两点间存在多条边的情况 ,取其中最短的一条

#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)

850. Dijkstra求最短路 II - AcWing

#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条边的最短路问题

853. 有边数限制的最短路 - AcWing题库

注意每次迭代将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)

851. spfa求最短路 - AcWing

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判断负环问题

852. spfa判断负环 - AcWing

#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;
	}
}

信使

1128. 信使 - AcWing 题库

题意:所有点到点1距离最短距离的最大值,如果有不连通输出-1

提醒:

        ①注意点数和边数的数据范围,尤其是边数,如果为无向边要在题目范围的基础上*2              ②如果没给边的范围,在n个点的图上,最多有\frac{n*(n-1)}{2}条边,无向边则需要*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();
}

最小花费

1126. 最小花费 - AcWing 题库

题意:两人之间转账需要扣除一定手续费,每两个人之间的手续费不同,问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();
}

最优乘车

920. 最优乘车 - AcWing题库

题意:有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;
}

昂贵的聘礼

903. 昂贵的聘礼 - AcWing题库

题意:有n个物品,每个物品可以由其他物品搭配一些金币来兑换,也可以直接金币购买,所有兑换的物品所属的等级差值最大为m,求得到物品1的最小花费

思路:建立一个超级源点,连向所有物品,边权为直接购买的花费,表示直接购买。物品之间建立单向边,表示兑换的花费。枚举交易的等级区间,L[1]为物品1的等级,要想得到,所有交易的等级必须在(L[1]-m,L[1])-(L[1],L[1]+m)之间,枚举长度为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;
}

新年好

1135. 新年好 - AcWing题库

题意:从点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;
}

通信线路

340. 通信线路 - AcWing题库

题意:求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;
}

道路与航线

342. 道路与航线 - AcWing题库

题意:

思路:将图分成若干个连通块,连通块内都是道路,连通块之间是航线

#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]);
    }
}

最优贸易

341. 最优贸易 - AcWing题库

题意:在每一点可以买入或卖出物品,物品价格在不同点不一样,只能交易一次,问从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;
} 

选择最佳线路

1137. 选择最佳线路 - AcWing题库

题意:可以选择多个起点到终点,求最短距离

思路:① 建立虚拟原点,和所有起点连长度为0的边,然后求最短路

           ② 反向建图,从终点开始求最短路,对比几个起点到终点的距离


最短路计数

1134. 最短路计数 - AcWing题库

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];
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Vic.GoodLuck

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值