C++图论基础单源最短路经典OJ题流食般投喂

本编的知识内容由小编手搓,以及资料均来自比特官网~


OJ题来源:洛谷

OJ题名:邮递员送信

OJ题归属:图论基础【单源最短路】

        解题算法:邻接矩阵存图 + 算法四选一、这里用的是常规版dijkstra算法

💡经验总结:

        话说是只需要 n - 1 循环即可求出 dist 数组的最终值,但是在这题里,如果在dijkstra算法里面求最终结果,只循环 n - 1 次是累加不出来最终正确的结果的,所以,我们最好循环 n 次哦!

        这里还用了一个做题技巧,就是存反图,这里是从起点走到 i 位置,再从 i 位置走回到起点位置,然后在开始其他趟的进行,这里我们如果不建反图的话,解决从 i 位置走到起点位置是比较麻烦的,如果我们建反图,我们再跑一遍dijkstra算法就行了,这波操作就属于:将“从不同位置走到同一终点”问题转化成了“单源最短路问题”

#include<iostream>
#include<cstring>

using namespace std;

const int N = 1e3;

int n, m;
int e[N][N]; // 邻接矩阵存图
int dist[N];
bool st[N];
int ret;

void dijkstra()
{
	// 初始化
	memset(dist, 0x3f, sizeof dist);
	memset(st, 0, sizeof st);
	dist[1] = 0;

	for (int i = 1; i <= n; i++)
	{
		int a = 0;
		for (int j = 1; j <= n; j++)
			if (!st[j] && dist[j] < dist[a])
				a = j;

		st[a] = true;

		for (int b = 1; b <= n; b++)
		{
			int c = e[a][b];

			if (dist[a] + c < dist[b])
				dist[b] = dist[a] + c;
		}

	}
}

int main()
{
	cin >> n >> m;
	memset(e, 0x3f, sizeof e);
	for (int i = 1; i <= m; i++)
	{
		int a, b, c; cin >> a >> b >> c;

		// 重边
		e[a][b] = min(e[a][b], c);
	}

	dijkstra();
	for (int i = 1; i <= n; i++) ret += dist[i];

	for (int i = 1; i <= n; i++)
		for (int j = i; j <= n; j++)
			swap(e[i][j], e[j][i]);

	dijkstra();
	for (int i = 1; i <= n; i++) ret += dist[i];

	cout << ret << endl;

	return 0;
}

OJ题来源:洛谷

OJ题名:采购特价商品

OJ题归属:图论基础【单源最短路】

        解题算法:vector存图 + 算法四选一、这里用的是常规版dijkstra算法

---存图时要计算边权值,然后正常跑一下 dijkstra 算法。

💡经验总结:

        这里的 dist 数组是 double 类型的,我们在给 dist 数组初始化的时候,不能再用memset进行 dist 数组的初始化了,要老老实实用 for 循环初始化~

        memset字节级填充工具,只适合字节类型、或 4 字节 int 特殊无穷大场景; double/long double 遵循 IEEE754 浮点编码,逐字节填充无法得到预期数值,不能用 memset 初始化无穷大。 只有 memset(arr, 0, sizeof(arr)) 可以安全清零浮点数组。

#include<iostream>
#include<vector>
#include<cmath>

using namespace std;

typedef pair<int, double> PID;

const int N = 110;

int n, m;
vector<PID> edges[N];
struct node
{
	int x, y;
}Coordinates[N]; // 存坐标
double dist[N];
bool st[N];
int tbegin, tend;

void dijkstra()
{
	// 初始化
	for (int i = 0; i <= n; i++) dist[i] = 1e10;
	dist[tbegin] = 0;

	for (int i = 1; i <= n; i++)
	{
		// 找“min”
		int a = 0;
		for (int j = 1; j <= n; j++)
			if (!st[j] && dist[j] < dist[a])
				a = j;

		st[a] = true;

		for (auto& e : edges[a])
		{
			int b = e.first; double c = e.second;

			if (dist[a] + c < dist[b])
			{
				dist[b] = dist[a] + c;
			}
		}
	}

	printf("%.2lf\n", dist[tend]);
}

int main()
{
	cin >> n;
	for (int i = 1; i <= n; i++)
	{
		int x, y; cin >> x >> y;
		Coordinates[i].x = x; Coordinates[i].y = y;
	}

	cin >> m;
	for (int i = 1; i <= m; i++)
	{
		int u, v; cin >> u >> v;
		int x1 = Coordinates[u].x, y1 = Coordinates[u].y;
		int x2 = Coordinates[v].x, y2 = Coordinates[v].y;
		double x = x1 - x2;
		double y = y1 - y2;
		double w = sqrt(x * x + y * y);

		edges[u].push_back({ v, w });
		edges[v].push_back({ u, w });
	}

	cin >> tbegin >> tend;

	dijkstra();

	return 0;
}

OJ题来源:洛谷

OJ题名:拉近距离

OJ题归属:图论基础【单源最短路】

        解题算法:可以解决负环的算法二选一、这里用的是 Bellman-Ford 算法

题目细节:

📌存边权存的是题目给的边权的相反数

📌爱情是双向的,以 1 为起点走到 n ;以 n 为起点走到 1 .

💡经验总结:

        💡判断负环时,dist 数组已经更新好了,因为已经执行了 n - 1 轮,前面执行的 n - 1 轮都是为了第 n 轮判断负环~ 而前 n - 1 轮是可以把 dist 数组完全更新的。

#include<iostream>
#include<vector>
#include<cstring>

using namespace std;

typedef pair<int, int> PII;

const int N = 1010;

int n, m;
vector<PII> edges[N];
int dist[N];

// BF 判断是否有负环,构造成带参的可以重复利用,
// 判断负环不论结果,dist 数组已经是以起点 s 更新好的了
bool bf(int s)
{
	// 初始化
	memset(dist, 0x3f, sizeof dist);
	dist[s] = 0;

	bool flag;
	for (int i = 1; i <= n; i++)
	{
		flag = false;
		for (int a = 1; a <= n; a++)
		{
			for (auto& e : edges[a])
			{
				int b = e.first, c = e.second;

				if (dist[a] + c < dist[b])
				{
					dist[b] = dist[a] + c;
					flag = true;
				}
			}
		}

		if (flag == false) return flag;
	}

	return flag;
}

int main()
{
	cin >> n >> m;
	for (int i = 1; i <= m; i++)
	{
		int s, t, w; cin >> s >> t >> w;
		
		edges[s].push_back({ t, -w });
	}

	int ret;
	if (bf(1))
	{
		cout << "Forever love" << endl;
		return 0;
	}
	ret = dist[n];

	if (bf(n)) 
	{
		cout << "Forever love" << endl;
		return 0;
	}
	ret = min(ret, dist[1]);

	cout << ret << endl;

	return 0;
}

OJ题来源:洛谷

OJ题名:最短路计数

OJ题归属:图论基础【单源最短路】

        解题算法:基于单元最短路算法实现的动态规划;由于要先确定一点已完成,且该题边权相等,可用最短路算法:bfs、dijkstra、spfa。如果边权不相等,可用最短路算法:dijkstra。本编实现了 bfs 与 堆优化版的dijkstra 两种。

题目细节:

📌重边要存

📌卡了输入输出那块的时间复杂度,要瘦身或者用 scanf/printf 

📌bfs 与 dijkstra 两种实现时,对 st 数组有不同的要求,要应算法而变

💡经验总结:

        💡这题的难点是动态规划的填表顺序,基于单源最短路的填表顺序,该题动态规划状态转移方程的更新是跟着松弛操作的判断同步的。dist[a] + 1 < dist[b] 的条件是第一次遍历到 b 点,而且 a 点已经确定了,dist[a] 的确定意味着 f[a] 的确定,f[b] = f[a];dist[a] + 1 == dist[b] 的条件是 不是第一次遍历到 b 点,最短路的条数要开始相加了,f[b] = f[a] + f[b]。

#include<iostream>
#include<vector>
#include<queue>
#include<cstring>

using namespace std;

typedef pair<int, int> PII;

const int N = 1e6 + 10, MOD = 100003;

int n, m;
vector<int> edges[N];
int dist[N];
bool st[N];
int f[N];

void bfs()
{
	// 初始化
	memset(dist, 0x3f, sizeof dist);
	f[1] = 1;
	dist[1] = 0;

	queue<int> q;
	q.push(1);
	// 不需要 st[i] = true ,因为这个点不只被遍历一次,打上标记就只能遍历一次了

	while (q.size())
	{
		auto a = q.front(); q.pop();

		for (auto& e : edges[a])
		{
			int b = e;

			if (dist[a] + 1 < dist[b])
			{
				// 第一次遍历到 b
				dist[b] = dist[a] + 1;
				f[b] = f[a];
				q.push(b);
			}
			else if (dist[a] + 1 == dist[b])
			{
				// 不是第一次遍历到
				f[b] = (f[a] + f[b]) % MOD;
			}
		}
	}
	
}

void dijkstra()
{
	// 初始化
	memset(dist, 0x3f, sizeof dist);
	f[1] = 1;
	dist[1] = 0;

	priority_queue<PII, vector<PII>, greater<PII>> heap; //小根堆
	heap.push({ 0, 1 });

	while (heap.size())
	{
		auto p = heap.top(); heap.pop();
		int a = p.second;

		if (st[a]) continue; // 已经确定过最短路的点就不用在更新一次了
							 // 不然,在 else if 那会多加
		st[a] = true;

		for (auto& e : edges[a])
		{
			int b = e;

			if (dist[a] + 1 < dist[b])
			{
				dist[b] = dist[a] + 1;
				f[b] = f[a];
				heap.push({ dist[b], b });
			}
			else if (dist[a] + 1 == dist[b])
			{
				f[b] = (f[a] + f[b]) % MOD;
			}
		}
	}
}

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);

	cin >> n >> m;
	for (int i = 1; i <= m; i++)
	{
		int x, y; cin >> x >> y;

		edges[x].push_back(y);
		edges[y].push_back(x);
	}

	//bfs();

	dijkstra();

	for (int i = 1; i <= n; i++) cout << f[i] << endl;

	return 0;
}
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值