kuangbin专题2:最短路径问题

A-Til the Cows Come Home
题目链接
思路:经典dijkstra问题
vis数组:标记,看是否得到过该点的最短路径
dis数组:记录下从起点到该点的最短路径,每次要更新
mp数组:存图,一定要注意初始化mp数组
步骤:
1、初始化各类辅助数据结构
2、外层1~n-1循环(找n-1次),内层找最小+更新当前最小
3、输出所要的结果

#include <iostream>
#include <stdio.h>
#define MAX 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int maxn = 1e3+10;
int t, n;
int a, b, c;
int mp[maxn][maxn];
int dis[maxn];
int vis[maxn];
void dijkstra(){
	vis[1]=1;
	for(int i=1; i<n; i++){
		int ans=MAX, k=1;
		for(int j=1; j<=n; j++){
			if(!vis[j]&&ans>dis[j]){
				ans=dis[j];
				k=j;
			}
		}
		vis[k]=1;
		for(int j=1; j<=n; j++){
			if(dis[j]>dis[k]+mp[k][j]) dis[j]=dis[k]+mp[k][j];
		}
	}
	cout << dis[n];
}
int main(){
	cin >> t >> n;
	for(int i=1; i<=n; i++){
		for(int j=1; j<=n; j++){
			if(i==j) continue;
			else mp[i][j]=MAX;
		}
	}
	for(int i=1; i<=t; i++){
		scanf("%d%d%d",&a,&b,&c);
		if(c<mp[a][b]){//这里要注意,可能一条路有多个输入,取最小的
			mp[a][b]=c, mp[b][a]=c;
		}
	}
	for(int i=1; i<=n; i++){
		dis[i]=mp[1][i];
	}
	dijkstra();
	return 0;
}

B-Frogger
题目链接
思路:该题意思是在所有通路中的最长边中寻找最短边即可
那么我们就把所有的道路长度存到一个w数组中,同时用a和b数组记下两点,最终用dijkstra更新n-1次,即可得到最小的必要距离。

#include <iostream>
#include <stdio.h>
#include <cmath>
#define MAX 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int maxn = 2e2+10;
int n;
struct node{
	int x,y;
}mp[maxn];
double dis[maxn], w[40010];
int a[40010], b[40010];
int main(){
	int k=0;
	while(cin >> n && n!=0){
		for(int i=1; i<=n; i++){
			scanf("%d %d",&mp[i].x,&mp[i].y);
		}
		int h=0;
		for(int i=1; i<n; i++){
			for(int j=i+1; j<=n; j++){
				w[++h]=sqrt((mp[i].x-mp[j].x)*(mp[i].x-mp[j].x)+(mp[i].y-mp[j].y)*(mp[i].y-mp[j].y));
				a[h]=i;
				b[h]=j;
			}
		}
		for(int i=1; i<=n; i++) dis[i]=MAX;
		dis[1]=0;
		for(int i=1; i<=n-1; i++){
			for(int j=1; j<=h; j++){
				if(dis[a[j]]>max(dis[b[j]],w[j])) dis[a[j]]=max(dis[b[j]],w[j]);
				if(dis[b[j]]>max(dis[a[j]],w[j])) dis[b[j]]=max(dis[a[j]],w[j]);
			}
		}
		printf("Scenario #%d\nFrog Distance = %.3f\n\n",++k,dis[2]);
	}
	return 0;
} 

C-Heavy Transportation
题目链接
思路:dijkstra的变形
问题的实质是叫我们找每条可到达n路径中所有最短边中的最大边,那么可以把dijkstra算法中的查询和更新部分进行改变,如下:
查询:

k=1;
		for(int j=1; j<=n; j++){
			if(!vis[j]&&dis[j]>dis[k]){
				k=j;
			}
		}
//vis数组标记是否走过,dis数组储存当前点到源点所有路径中最短边的最长边。
//为什么选大的,原因是我要找最短边中的最大边,所以要把最大变得更大,才可以用它把别的值变得更大。
for(int j=1; j<=n; j++){
			if(!vis[j]&&dis[j]<min(dis[k],mp[k][j])){
				dis[j]=min(dis[k],mp[k][j]);
			}
		}
//最小边中的最大边,所以我们要取min找最小边,然后dis中的当前最优解比min中的最小边还小,那么我们就要刷新。
#include <iostream>
#include <stdio.h>
#include <cmath>
#include <cstring>
#define MAX 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int maxn = 1e3+10;
int n, m, u, v, w, h, t;
int mp[maxn][maxn];
int dis[maxn];
int vis[maxn];
void dijkstra(){
	vis[1]=1;
	int ans, k;
	for(int i=1; i<n; i++){
		k=1;
		for(int j=1; j<=n; j++){
			if(!vis[j]&&dis[j]>dis[k]){
				k=j;
			}
		}
		vis[k]=1;
		for(int j=1; j<=n; j++){
			if(!vis[j]&&dis[j]<min(dis[k],mp[k][j])){
				dis[j]=min(dis[k],mp[k][j]);
			}
		}
	}
	printf("Scenario #%d:\n%d\n\n",++h,dis[n]);
}
int main(){
	cin >> t;
	while(t--){
		cin >> n >> m;
		for(int i=1; i<=n; i++){
			for(int j=1; j<=n; j++){
				mp[i][j]=0; 
			}
		}
		memset(vis,0,sizeof(vis));
		for(int i=1; i<=m; i++){
			scanf("%d%d%d",&u,&v,&w);
			mp[u][v]=mp[v][u]=w;
		}
		for(int i=1; i<=n; i++){
			dis[i]=mp[1][i];
		}
		dijkstra();
		for(int i=1; i<=n; i++){
			cout << dis[i] << " ";
		}
	}
	return 0;
} 

D-Silver Cow Party
题目链接
思路:可以看到这题有多头牛往一个终点跑,然后从终点再回来,找来回用时最长的一头牛所花费的时间,且该图为单向图。
在普通的dijkstra中,我们的dis数组存的是每个点到源点的最短边权,但是在这里我们可以看到是多个牛到一个终点然后再回来,所以我们的dis数组可以用来存终点到每个牛的最短边权,然后将图转置,接着用dis数组存每个牛到终点的最短边权(其实就是反过来求最短边权),最后进行比较即可。

#include <iostream>
#include <stdio.h>
#include <cstring>
#include <cmath>
#define MAX 0x3f3f3f3f
using namespace std;
const int maxn = 1e3+10;
typedef long long ll;
int n, m, x, a, b, t;
int vis[maxn], dis[maxn], disx[maxn];
int mp[maxn][maxn];
void dijkstra(){
	memset(vis,0,sizeof(vis));
	for(int i=1; i<=n; i++) dis[i]=mp[x][i];
	vis[x]=1;
	for(int i=1; i<n; i++){
		int minx=MAX, k=1;
		for(int j=1; j<=n; j++){
			if(!vis[j]&&minx>dis[j]){
				minx=dis[j], k=j;
			}
		}
		vis[k]=1;
		for(int j=1; j<=n; j++){
			if(!vis[j]&&dis[j]>dis[k]+mp[k][j]) dis[j]=dis[k]+mp[k][j];
		}
	}
}
int main(){
	cin >> n >> m >> x;
	for(int i=1; i<=n; i++){
		for(int j=1; j<=n; j++){
			if(i==j) mp[i][j]=0;
			else mp[i][j]=MAX;
		}
	}
	for(int i=1; i<=m; i++){
		scanf("%d%d%d",&a,&b,&t);
		if(mp[a][b]>t) mp[a][b]=t;
	}
	dijkstra();
	for(int i=1; i<=n; i++) disx[i]=dis[i];
	for(int i=1; i<=n; i++){
		for(int j=i+1; j<=n; j++){//注意一下一定是i+1,如果不是i+1,会转回去,会乱
			int temp=mp[j][i];
			mp[j][i]=mp[i][j];
			mp[i][j]=temp;
		}
	}
	dijkstra();
	int ans=0;
	for(int i=1; i<=n; i++){
		if(ans<dis[i]+disx[i]) ans=dis[i]+disx[i];
	}
	printf("%d",ans);
	return 0;
}

E-Wormholes
题目链接
思路:这种题模板题,是否能够回到起点,取决于是否有负环,spfa和bell_man_ford都可以做

Bell_man_Ford

#include <iostream>
#include <stdio.h>
#include <cstring>
#include <cmath>
#define MAX 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int maxn = 1e4+10;//一定要开足够,否则会RE
struct node{
	int x, y;
	int val;
}v[maxn];
int n, m, w, f;
int s, e, t;
int dis[maxn];
int k;
bool Bell_man_Ford(){
	for(int i=1; i<=n; i++) dis[i]=MAX;
	dis[1]=0;
	for(int i=1; i<n; i++){
		for(int j=1; j<=k; j++){
			if(dis[v[j].y]>dis[v[j].x]+v[j].val){
				dis[v[j].y]=dis[v[j].x]+v[j].val;
			}
		}
	}
	for(int i=1; i<=k; i++){
		if(dis[v[i].y]>dis[v[i].x]+v[i].val){
			return false;
		}
	}
	return true;
}
int main(){
	cin >> f;
	while(f--){
		k=0;
		scanf("%d%d%d",&n,&m,&w);
		for(int i=1; i<=m; i++){
			scanf("%d%d%d",&s,&e,&t);
			v[++k].x=s, v[k].y=e, v[k].val=t;
			v[++k].x=e, v[k].y=s, v[k].val=t;
		}
		for(int i=1; i<=w; i++){
			scanf("%d%d%d",&s,&e,&t);
			v[++k].x=s, v[k].y=e, v[k].val=-t;
		}
		if(Bell_man_Ford()) puts("NO");
		else puts("YES");
	}
	return 0;
}

F-Cow Contest
题目链接
思路:可以看到,我们是要寻找每一个牛是否能准确得到排名,那类似一个多源最短路问题,数据不大,n<500, 那Floyd可以用上。
解题关键:只要一头牛有n-1条胜负关系,那就可以确定他的排名。
方法:mp二维数组,谁赢谁标1,然后Floyd走一遍,最后判断即可。

#include <iostream>
using namespace std;
const int maxn = 1e2+10;
int n, m;
int mp[maxn][maxn];
int a, b;
int main(){
	cin >> n >> m;
	for(int i=1; i<=m; i++){
		cin >> a >> b;
		mp[a][b]=1;
	}
	for(int k=1; k<=n; k++)
		for(int i=1; i<=n; i++)
			for(int j=1; j<=n; j++)	if(mp[i][k]&&mp[k][j]) mp[i][j]=1;
	int sum=0;
	for(int i=1; i<=n; i++){
		int ans=0;
		for(int j=1; j<=n; j++){
			if(mp[i][j]||mp[j][i]) ans++; 
			//注意这里是或,因为要确定n-1个1,而关系是双向的,因此只要一个成立即可
		}
		if(ans==n-1) sum++;
	}
	cout << sum;
	return 0;
}

G-Arbitrage
题目链接
思路:套利的意思是原本自己投入的财富最后出现了增长,就如给的test1,1美元走一圈后变成1.05美元。
把所有信息建成图,那么意思就是这其中存在一个环,回去之后使本钱增加,然后因为本钱增加,导致后面一系列的更新使本钱再增加,松弛n-1次后,第n次如果还能增加,则表明其中存在环,用dis数组记录本金能换取该货币的值,dis【1】存本金。

#include <iostream>
#include <stdio.h>
#include <cstring>
#include <map>
#define MAX 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int maxn = 3e4+10;
int n, m;
string s;
string str1, str2;
double rate;
struct node{
	int st, ed;
	double rat;
}mp[maxn];
map <string,int> k;
double dis[maxn];
bool Bell_man_Ford(){
	memset(dis,0,sizeof(dis));
	dis[1]=1;
	for(int i=1; i<=n; i++){
		for(int j=1; j<=m; j++){
			if(dis[mp[j].ed]<dis[mp[j].st]*mp[j].rat){
				dis[mp[j].ed]=dis[mp[j].st]*mp[j].rat;
				//当到第n次还能更新,说明其中存在环
				if(i==n) return true;
			}
		}
	}
	return false;
}
int main(){
	int Case=0;
	while(cin >> n && n){
		for(int i=1; i<=n; i++){
			cin >> s;
			k[s]=i;
		}
		cin >> m;
		for(int i=1; i<=m; i++){
			cin >> str1 >> rate >> str2;
			mp[i].st=k[str1], mp[i].ed=k[str2], mp[i].rat=rate;
		}
		if(Bell_man_Ford()) printf("Case %d: Yes\n",++Case);
		else printf("Case %d: No\n",++Case);
	}
	return 0;
}
for(int i=1; i<=n; i++){
		for(int j=1; j<=m; j++){
			if(dis[mp[j].ed]<dis[mp[j].st]*mp[j].rat){
				dis[mp[j].ed]=dis[mp[j].st]*mp[j].rat;
				if(i==n) return true;
			}
		}
	}

个人觉得这里可以改成如下:

for(int j=1; j<=m; j++){
	if(dis[mp[j].ed]<dis[mp[j].st]*mp[j].rat){
		dis[mp[j].ed]=dis[mp[j].st]*mp[j].rat;
		if(i==n) return true;
	}
}

无法证明正确性,毕竟数据过于水,但改完能过,请大佬指点。

题目还有一种做法,用Floyd做
插入一段代码

for(int k=1; k<=n; k++){
		for(int i=1; i<=n; i++){
			for(int j=1; j<=n; j++){
				if(mp[i][j]<mp[i][k]*mp[k][j]) mp[i][j]=mp[i][k]*mp[k][j];
			}
		}
	}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值