单源最短路的建图方式(万能的spfa)

热浪:

题目大致意思:

给你n个点m条边,给你一个起始的位置,一个终点的位置,问你这两个点的最短距离

朴素的Dijkstra算法:

代码:

#include<bits/stdc++.h>

using namespace std;

const int N = 2510;

int n, c, ts, te;
string s;
int dis[N];//距离起点的最短距离 
int g[N][N];
bool st[N];//这个点的距离是否已经被确定了 

void dijkstra() {
	for(int i = 1; i <= n; ++ i ) {//n次迭代 
		int t = -1;
		for(int j = 1; j <= n; ++ j ) {
			if((t == -1 || dis[j] < dis[t]) && !st[j]) {
				t = j;
			}
		}
		st[t] = true;
		for(int j = 1; j <= n; ++ j ) {
			dis[j] = min(dis[j], dis[t] + g[t][j]);
		}
	}
}

signed main() {
	cin >> n >> c >> ts >> te;
	memset(g, 0x3f, sizeof g);
	memset(dis, 0x3f, sizeof dis);
	dis[ts] = 0;
	
	for(int i = 1; i <= c; ++ i ) {
		int x, y, v;
		cin >> x >> y >> v;
		g[x][y] = g[y][x] = min(g[x][y], v);//双向的 
	}
	dijkstra();
	cout << dis[te] << endl;
	return 0;
}

运行的时间:239ms


spfa算法:

注意: 题目说的是无向图,因此边数要乘以2,否则会TLE

代码:

#include<bits/stdc++.h>

using namespace std;

const int N = 2510, M = 6200 * 2 + 10;//边数要开2倍 因为是无向图 

int h[N], e[M], ne[M], v[M], idx;
int t, c, ts, te;
queue<pair<int, int>>q;
int dis[N];
bool st[N];

void add(int x, int y, int va) {
	e[idx] = y;
	ne[idx] = h[x];
	v[idx] = va;
	h[x] = idx++;
}

void spfa() {
	dis[ts] = 0;//起点的距离置为0
	q.push({0, ts});
	st[ts] = true;
	while(q.size()) {
		auto p = q.front();
		int d = p.second, va = p.first;
		q.pop();
		st[d] = false;//出去了
		for(int i = h[d]; i != -1; i = ne[i]) {
			int j = e[i];
			if(dis[j] > dis[d] + v[i]) {//这里写的是v[i]表示的是这两个点之间的距离 
				dis[j] = dis[d] + v[i];
				if(!st[j]) {
					q.push({dis[j], j});
					st[j] = true;
				}
			}
		}
	} 
} 

signed main() {
	cin >> t >> c >> ts >> te;
	memset(h, -1, sizeof h);
	memset(dis, 0x3f, sizeof dis);
	for(int i = 1; i <= c; ++ i ) {
		int x, y, va;
		cin >> x >> y >> va;
		add(x, y, va);
		add(y, x, va);//无向图 
	}
	spfa();
	cout << dis[te] << endl;
	return 0;
}

运行的时间:31ms

因此可以得出:

一般情况下spfa是比Dijkstra要快的,快的马上要10倍了,但是有的时候,spfa会被卡,要是被卡的话,时间复杂度会达到O(nm)

香甜的黄油:

题目大致意思:

给你一个连通的图, 让你随便选择起点(但是最后的答案要小), 让你求其他点到这个起点的和的最小值 

分析:

去枚举每个牧场让他们每个都作为起点,然后求出来最小的答案,用朴素版的Dijkstra是会卡的, 但是优化一下,也就是优化版的Dijkstra就可以过了,或者可以用几乎万能的spfa()

代码:

#include<bits/stdc++.h>

using namespace std;

const int N = 810, M = 1450 * 2 + 10;//注意是无向边 

int n, p, c;
int id[N], h[N], e[M], w[M], ne[M], idx;
bool st[N];
int dis[N];

void add(int x, int y, int v) {
	e[idx] = y;
	w[idx] = v;
	ne[idx] = h[x];
	h[x] = idx++;
}

int spfa(int x) {
	queue<pair<int, int>>q;
	dis[x] = 0;//作为起点 
	q.push({0, x});
	st[x] = true;
	
	while(q.size()) {
		auto t = q.front();
		q.pop();
		int dian = t.second;
		st[dian] = false;//出去了 
		
		for(int i = h[dian]; i != -1; i = ne[i]) {
			int j = e[i];
			if(dis[j] > dis[dian] + w[i]) {
				dis[j] = dis[dian] + w[i];
				if(!st[j]) {
					q.push({dis[j], j});
					st[j] = true;
				}
			}
		}
	}
	int ret = 0;
	for(int i = 1; i <= n; ++ i ) {
		int j = id[i];
		if(dis[j] == 0x3f3f3f3f)return 0x3f3f3f3f;
		ret += dis[j];
	}
	return ret;
}

signed main() {
	cin >> n >> p >> c;//奶牛数 牧场数 道路数
	for(int i = 1; i <= n; ++ i ) {
		cin >> id[i];//记录的是牛棚(有牛在那) 
	}
	memset(h, -1, sizeof h);
	for(int i = 1; i <= c; ++ i ) {//牛棚和牛棚之间的距离 
		int x, y, z;
		cin >> x >> y >> z;
		add(x, y, z);
		add(y, x, z);
	}
	int ret = 0x3f3f3f3f;
	for(int i = 1; i <= p; ++ i ) {//这里枚举的是牧场的数量 
		memset(dis, 0x3f, sizeof dis);
		memset(st, 0, sizeof st);
		ret = min(ret, spfa(i));//枚举每个i作为起点 
	}
	cout << ret << endl;
	return 0;
}

运行时间:526ms

最小花费

题目大致意思:

给你n个人,给你m个关系 表示这两个人之间转账需要的手续率

问你最少需要多少钱使得转账后B可以收到100元

分析:

之前做的都是边的问题,让你求最短路,现在是同样的板子,只不过换了个问法

逆向思维,问题转换为,给B 100元,每次转钱都给你手续费,问你转到A的时候的钱是多少

因此也就是把之前的dis换为了ret数组(表示的是由B转过来的钱数)

	for(int i = h[t]; i != -1; i = ne[i])  {
			int j = e[i];
			if(ret[j] > ret[t] / (1.0 - w[i] * 0.01) ) {
				ret[j] = ret[t] / (1.0 - w[i] * 0.01);
				if(!st[j]) {
					q.push(j);
					st[j] = true;//进去了 
				} 
			}
		}

代码:

#include<bits/stdc++.h>

using namespace std;

const int N = 2010, M = 2e5 + 10;

int h[N], e[M], ne[M], w[M], idx;
int n, m;
bool st[N];
double ret[N];

void add(int x, int y, int v) {
	e[idx] = y;
	w[idx] = v;
	ne[idx] = h[x];
	h[x] = idx++;
}

void spfa(int x) {
	queue<int>q;
	q.push(x);
	st[x] = true;
	ret[x] = 100.0;
	
	while(q.size()) {
		int t = q.front();
		q.pop();
		st[t] = false;//出去了
		for(int i = h[t]; i != -1; i = ne[i])  {
			int j = e[i];
			if(ret[j] > ret[t] / (1.0 - w[i] * 0.01) ) {
				ret[j] = ret[t] / (1.0 - w[i] * 0.01);
				if(!st[j]) {
					q.push(j);
					st[j] = true;//进去了 
				} 
			}
		}
	}
}

signed main() {
	cin >> n >> m;
	memset(h, -1, sizeof h);
	for(int i = 1; i <= n; ++ i ) {
		ret[i] = 1e9;
	} 
	for(int i = 1; i <= m; ++ i ) {
		int x, y, v;
		cin >> x >> y >> v;
		add(x, y, v);
		add(y, x, v);//无向图 
	}
	
	int start, end;
	cin >> start >> end;
	
	spfa(start);
	printf("%.8lf", ret[end]);
	
	return 0;
}

运行时间:235 ms

最优乘车 :

题目大致意思:

给你n个公交车的路线,让你求从1到n最少换乘车的次数

分析:

转换思路, 用dis[i]表示的是1到i需要几步能到达的点,要是在同一班车的话,我们就认为是1步,答案就是dis[n] - 1

但是这里他没给你每一班车的数量 我们就用到了  stringstream (字符串流)这个字符串容器,也可以认为是一串分割后的字符串数组

  • 可以自动分割字符串,把字符串中的  空格省略
  • 可以在移出字符串流后会    自动类型转换

 stringstream (字符串流)的代码演示:

#include<bits/stdc++.h>
using namespace std;

string s;

signed main() {
	s = "code change the word";
	stringstream ssin(s);//让这个字符串 进到名字叫ssin的符串容器里面 
	
	string t;//暂时用来接收的 
	//我在把每个字符给吐出来 吐出来给谁的 你让我给是字符串p 我就自动给你转换为字符串的类型 还除去空了啦 
	while(ssin >> t)cout << t << endl; 
	return 0;
}

 stringstream (字符串流)运行结果:

code
change
the
word

代码: 

#include<bits/stdc++.h>

using namespace std;

const int N = 510;
int m, n ;
bool g[N][N];
int dis[N];// dis[i] 表示的是1到i需要几步能到达的点 要是再同一班车能经过的话 我们就认为1步就可以 
int stop[N];

void bfs() {//和spfa一个样子 spfa还是牛 
	queue<int>q;
	memset(dis, 0x3f, sizeof dis);
	q.push(1);
	dis[1] = 0;//起点是1号点 
	
	while(q.size()) {
		int t = q.front();
		q.pop();
		
		for(int i = 1; i <= n; ++ i ) {
			if(g[t][i] && dis[i] > dis[t] + 1) {
				dis[i] = dis[t] + 1;//第一次搜到的就是最短的
				q.push(i);
			}
		}
	}
}


signed main() {
	
	cin >> m >> n;
	//建图是最难的 这m个 怎么能去读入数据呢
	string line;
	getline(cin, line);//吸收掉回车 
	
	while(m--) {
		getline(cin, line);//因为你不知道他一行会给你多少个 这个遇到回车会停止读入的 
		stringstream ssin(line);
		int cnt = 0, p ;
		while(ssin >> p)stop[++cnt] = p;
		
		//每个汽车的前站达到后站的距离都为1 因为是同一辆车 未换车
		for(int j = 1; j <= cnt; ++ j ) {
			for(int k = j + 1; k <= cnt; ++ k ) {
				g[stop[j]][stop[k]] = true;
			}
		}
	}
	bfs();
	if(dis[n] > 0x3f3f3f3f / 2) {
		cout << "NO" << endl;
		return 0;
	}
	cout << max(dis[n] - 1, 0) << endl;//换乘的次数 就是距离 - 1 
	return 0;
}
/*
这里用到了 stringstream (字符串流)这个字符串容器,也可以认为是一串分割后的字符串数组
它的作用:
可以自动分割字符串,把字符串中的  空格省略
可以在移出字符串流后会    自动类型转换

建图 每个车站到达后面的车站的距离都是1
那么1到达n的最短距离就是转车次数+1
*/

运行时间: 17ms

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

FindYou.

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

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

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

打赏作者

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

抵扣说明:

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

余额充值