最短路及衍生问题

目录

最短路

Floyd

Dijkstra

SPFA

最短路-分层图

最短路计数

次短路

最长路


最短路

Floyd

经典三重循环,求多源最短路

#include<iostream>
using namespace std;
int d[1000][1000];
int main()
{
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			if(i==j)
				d[i][j]=0;
			else
				d[i][j]=0x3f3f3f3f;
	for(int i=1;i<=m;i++)
	{
		int u,v,w;
		cin>>u>>v>>w;
		d[u][v]=w;
		d[v][u]=w;
	}
	for(int k=1;k<=n;k++)
		for(int i=1;i<=n;i++)
			for(int j=1;j<=n;j++)
				d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
}

可以用Floyd传递闭包,判断连通性,总之,看到n的数据范围较小时,便可以向Floyd上靠

Dijkstra

经典求单源最短路

#include<iostream>
#include<vector>
#include<queue>
#include<cstring>
using namespace std;
struct node
{
	int v,w;
};
vector<node> g[1000000];
priority_queue<pair<int,int> > q;
int dis[10000000],vis[10000000];
int main()
{
	int n,m;
	cin>>n>>m;
	memset(dis,0x3f,sizeof(dis));
	dis[1]=0;
	q.push({0,1});
	for(int i=1;i<=m;i++)
	{
		int u,v,w;
		cin>>u>>v>>w;
		g[u].push_back({v,w});
	}
	while(q.size())
	{
		auto p=q.top();
		q.pop();
		int u=p.second;
		if(vis[u])
			continue;
        vis[u]=1;
		for(auto it:g[u])
		{
			int v=it.v,w=it.w;
			if(dis[v]>dis[u]+w)
			{
				dis[v]=dis[u]+w;
				q.push({-dis[v],v});
			}
		}
	}
}

因为使用了优先队列,于是在第39行需要进负数,使得在负数状态下最大,也就是正数状态下最小

应用较广泛,不容易被卡

SPFA

关于SPFA,它死了

SPFA很容易被卡,如菊花图,方格图。

于是,SPFA唯一的作用好像就是判断负环了

#include<iostream>
#include<queue>
#include<cstring>
#include<vector>
using namespace std;
struct node
{
	int v,w;
};
vector<node> g[1000000];
queue<int> q;
int cnt[10000000],dis[10000000],vis[10000000];
int main()
{
	int n,m;
	cin>>n>>m;
	memset(dis,0x3f,sizeof(dis));
	dis[1]=0;
	vis[1]=1;
	q.push(1);
	for(int i=1;i<=m;i++)
	{
		int u,v,w;
		cin>>u>>v>>w;
		g[u].push_back({v,w});
	}
	while(q.size())
	{
		int u=q.front();
		q.pop();
		vis[u]=0;
		for(auto it:g[u])
		{
			int v=it.v,w=it.w;
			if(dis[v]>dis[u]+w)
			{
				dis[v]=dis[u]+w;
				cnt[v]=cnt[u]+1;
				if(cnt[v]>=n)
				{
					cout<<"负环";
					return 0; 
				}
				if(!vis[v])
				{
					q.push(v);
					vis[v]=1;
				}
			}
		}
	}
}

在这里,我们引出 差分约束 即形如 j\leqslant i+C的不等式组 由于这些不等式与松弛操作的 if 判断相似,于是我们边可以建立从 i 到 j 的有向边,权值为 C ,再求最短路,由于不确定 C 的正负,所以我们便使用SPFA来求,若有负环,则无解。若不连通,则有很多个解

(蒟蒻不会证明)

最短路-分层图

在我们做题的时候,时不时就会碰见那些无赖,比如免费坐飞机。

于是我们便引出分层图

即 有K次机会花费权值为一个特殊值 我们便可以建立  K+1 张图,每层之间边使用特殊权值,每层之内的边使用正常权值,我们便拿无赖坐飞机举例

P4568

#include<iostream>
#include<cstring>
#include<queue>
#include<vector>
using namespace std;
struct node
{
	int v,w;
};
vector<node> g[1000000];
priority_queue<pair<int,int> > q;
int dis[10000000],vis[10000000];
int main()
{
	int n,m,k,s,t;
	cin>>n>>m>>k>>s>>t;
	memset(dis,0x3f,sizeof(dis));
	dis[s]=0;
	q.push({0,s});
	for(int i=1;i<=m;i++)
	{
		int u,v,w;
		cin>>u>>v>>w;
		g[u].push_back({v,w});
		g[v].push_back({u,w});
		for(int j=1;j<=k;j++)
		{
			g[u+(j-1)*n].push_back({v+j*n,0});
			g[v+(j-1)*n].push_back({u+j*n,0});
			g[u+j*n].push_back({v+j*n,w});
			g[v+j*n].push_back({u+j*n,w});
		}
	}
	while(q.size())
	{
		auto p=q.top();
		q.pop();
		int u=p.second;
		if(vis[u])
			continue;
		for(auto it:g[u])
		{
			int v=it.v,w=it.w;
			if(dis[v]>dis[u]+w)
			{
				dis[v]=dis[u]+w;
				q.push({-dis[v],v});
			}
		}
	}
	int minn=0x3f3f3f3f;
	for(int i=0;i<=k;i++)
		minn=min(minn,dis[t+i*n]);
	cout<<minn;
}

(蒟蒻不知道为什么最后要取最小值)

这个方法占空间比较大,所以还是数组尽量开大一点比较好

最短路计数

顾名思义

P1144

若到 v 点的最短路与松弛后的相等,就证明多了一条路

那就继承

ans[v]=ans[v]+ans[u]

若正常松弛,那证明到 v 点的路径数量与到 u 点的相等

那就覆盖

ans[v]=ans[u]

#include<iostream>
#include<vector>
#include<queue>
#include<cstring>
using namespace std;
struct node
{
	int v,w;
};
vector<node> g[1000000];
priority_queue<pair<int,int> > q;
int ans[10000000],dis[10000000],vis[10000000];
int main()
{
	int n,m;
	cin>>n>>m;
	memset(dis,0x3f,sizeof(dis));
	dis[1]=0;
	q.push({0,1});
	ans[1]=1;
	for(int i=1;i<=m;i++)
	{
		int u,v;
		cin>>u>>v;
		g[u].push_back({v,1});
		g[v].push_back({u,1});
	}
	while(q.size())
	{
		auto p=q.top();
		q.pop();
		int u=p.second;
		if(vis[u])
			continue;
		vis[u]=1;
		for(auto it:g[u])
		{
			int v=it.v,w=it.w;
			if(dis[v]==dis[u]+w)
				ans[v]=(ans[u]+ans[v])%100003;
			if(dis[v]>dis[u]+w)
			{
				ans[v]=ans[u];
				dis[v]=dis[u]+w;
				q.push({-dis[v],v});
			}
		}
	}
	for(int i=1;i<=n;i++)
		cout<<ans[i]<<endl;
}

次短路

怎么更新次短路?

1.在松弛操作成功后,松弛前的最短路便大于松弛后的最短路,于是次短路便更新为松弛前的最短路

2.松弛操作失败,但松弛后的路径比原来的次短路要小,于是更新次短路

3.次短路更新次短路

P2865

#include<iostream>
#include<cstring>
#include<queue>
#include<vector>
using namespace std;
struct node
{
	int v,w;
};
vector<node> g[1000000];
queue<int> q;
int dis[10000000],diss[10000000],vis[10000000];
int main()
{
	int n,m;
	cin>>n>>m;
	memset(dis,0x3f,sizeof(dis));
	memset(diss,0x3f,sizeof(diss));
	dis[1]=0;
	q.push(1);
	vis[1]=1;
	for(int i=1;i<=m;i++)
	{
		int u,v,w;
		cin>>u>>v>>w;
		g[u].push_back({v,w});
		g[v].push_back({u,w});
	}
	while(q.size())
	{
		int u=q.front();
		q.pop();
		vis[u]=0;
		for(auto it:g[u])
		{
			int v=it.v,w=it.w;
			if(dis[v]>dis[u]+w)
			{
				diss[v]=dis[v];
				dis[v]=dis[u]+w;
				if(!vis[v])
				{
					vis[v]=1;
					q.push(v);
				}
			}
			if(dis[v]<dis[u]+w&&diss[v]>dis[u]+w)
			{
				diss[v]=dis[u]+w;
				if(!vis[v])
				{
					q.push(v);
					vis[v]=1;
				}
			}
			if(diss[v]>diss[u]+w)
			{
				diss[v]=diss[u]+w;
				if(!vis[v])
				{
					
					q.push(v);
					vis[v]=1;
				}
			}
		}
	}
	cout<<diss[n];
}

最长路

顾名思义

我们可以在读边权的时候改成负的,再正常求最短路,最后把负的再负回去

#include<iostream>
#include<queue>
#include<cstring>
using namespace std;
struct node
{
	int v,w;
};
vector<node> g[100000];
queue<int> q;
int dis[10000000],vis[10000000],cnt[10000000];
int main()
{
	int n,m;
	cin>>n>>m;
	memset(dis,0x3f,sizeof(dis));
	dis[1]=0;
	q.push(1);
	vis[1]=1;
	for(int i=1;i<=m;i++)
	{
		int u,v,w;
		cin>>u>>v>>w;
		g[u].push_back({v,w});
//		g[v].push_back({u,w});
	}
	while(q.size())
	{
		int u=q.front();
		q.pop();
		vis[u]=0;
		for(auto it:g[u])
		{
			int v=it.v,w=-it.w;
			if(dis[v]>dis[u]+w)
			{
				dis[v]=dis[u]+w;
				cnt[v]=cnt[u]+1;
				if(cnt[v]>=n)
				{
					cout<<-1;
					return 0;
				}
				q.push(v);
				vis[v]=1;
			}
		}
	}
	if(dis[n]==0x3f3f3f3f)
	{
		cout<<-1;
		return 0;
	}
	cout<<-dis[n];
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值