通往奥格瑞玛的道路(C++,Dijkstra,二分)

题目背景

在艾泽拉斯大陆上有一位名叫歪嘴哦的神奇术士,他是部落的中坚力量。

有一天他醒来后发现自己居然到了联盟的主城暴风城。

在被众多联盟的士兵攻击后,他决定逃回自己的家乡奥格瑞玛。

题目描述

在艾泽拉斯,有 n n n 个城市。编号为 1 , 2 , 3 , … , n 1,2,3,\ldots,n 1,2,3,,n

城市之间有 m m m 条双向的公路,连接着两个城市,从某个城市到另一个城市,会遭到联盟的攻击,进而损失一定的血量。

每次经过一个城市,都会被收取一定的过路费(包括起点和终点)。路上并没有收费站。

假设 1 1 1 为暴风城, n n n 为奥格瑞玛,而他的血量最多为 b b b,出发时他的血量是满的。如果他的血量降低至负数,则他就无法到达奥格瑞玛。

歪嘴哦不希望花很多钱,他想知道,在可以到达奥格瑞玛的情况下,他所经过的所有城市中最多的一次收取的费用的最小值是多少。

输入格式

第一行 3 3 3 个正整数, n , m , b n,m,b n,m,b。分别表示有 n n n 个城市, m m m 条公路,歪嘴哦的血量为 b b b

接下来有 n n n 行,每行 1 1 1 个正整数, f i f_i fi。表示经过城市 i i i,需要交费 f i f_i fi 元。

再接下来有 m m m 行,每行 3 3 3 个正整数, a i , b i , c i a_i,b_i,c_i ai,bi,ci 1 ≤ a i , b i ≤ n 1\leq a_i,b_i\leq n 1ai,bin)。表示城市 a i a_i ai 和城市 b i b_i bi 之间有一条公路,如果从城市 a i a_i ai 到城市 b i b_i bi,或者从城市 b i b_i bi 到城市 a i a_i ai,会损失 c i c_i ci 的血量。

输出格式

仅一个整数,表示歪嘴哦交费最多的一次的最小值。

如果他无法到达奥格瑞玛,输出 AFK

样例 #1

样例输入 #1

4 4 8
8
5
6
10
2 1 2
2 4 1
1 3 4
3 4 3

样例输出 #1

10

提示

对于 60 % 60\% 60% 的数据,满足 n ≤ 200 n\leq 200 n200 m ≤ 1 0 4 m\leq 10^4 m104 b ≤ 200 b\leq 200 b200

对于 100 % 100\% 100% 的数据,满足 n ≤ 1 0 4 n\leq 10^4 n104 m ≤ 5 × 1 0 4 m\leq 5\times 10^4 m5×104 b ≤ 1 0 9 b\leq 10^9 b109

对于 100 % 100\% 100% 的数据,满足 c i ≤ 1 0 9 c_i\leq 10^9 ci109 f i ≤ 1 0 9 f_i\leq 10^9 fi109,可能有两条边连接着相同的城市。

解题思路:

这道题就是求解路径中最大点权的最小值,像是这种描述,十有八九是二分了

给出一张图,每个节点有各自的点权,每条边有各自的边权

我们希望有一条从1n的路径,使其每个点的点权尽可能的小同时边权和不大于一定的值(血量)

已经知道题意了,接下来考虑如何实现

最简单的方式就是暴力搜索,枚举每一条能够从1n的路径,找出其中最大点权值最小的即可,找不到路径就输出AKF

/* 代码五分钟,运行两小时 */

所以我们必须优化时间

为什么二分法是适用的呢?

我们现在添加一个每次的花费金额上限到路径要求中,可以直观的感受到

上限越高,我们所能尝试的路径种类就越多,我们就越有可能到达奥格瑞玛

同时我们又希望每次花费金额上限尽可能的低

所以二分法适用

采用二分法搜索最大点权限制,如果能够到达奥格瑞玛,那么尝试进一步限制,如果不能到达奥格瑞玛,尝试稍微解除限制

再辅以Dijkstra的最短路径算法,本题就解决了

AC代码如下

#include <iostream>
#include <memory.h>
#include <vector>
#include <queue>
using namespace std;
const int max_n = 1e4;
const int max_m = 5e4;
const int max_b = 1e9;
const int NaN = 0x3F3F3F3F;

class greater_queue {
public:
	bool operator()(pair<int, int>p_1, pair<int, int>p_2) const {
		return p_1.first > p_2.first;
	}
};

priority_queue<pair<int, int>, vector<pair<int, int>>, greater_queue>p_q;//Dijkstra
int cost[max_n + 1] = { 0 };//收费站
struct edge { int v, p, next; }edges[2 * max_m];//链式前向星存图
int head[max_n + 1] = { -1 };
int tot = -1;
bool book[max_n + 1] = { false };//最短路径集
int n, m, b;

void add_edge(int u, int v, int p) {
	edges[++tot] = { v,p,head[u] }; head[u] = tot;
	edges[++tot] = { u,p,head[v] }; head[v] = tot;
}

bool dijkstra(const int limit) {
	if (cost[1] > limit) return false;//曾言道:“起点都过不了走个p”
	//初始化
	memset(book + 1, false, sizeof(bool) * max_n);
	p_q.push(pair<int, int>(0, 1));//pair<int, int>(最短距离, 节点)

	while (!p_q.empty()) {
		int node = p_q.top().second;
		int dist = p_q.top().first;
		p_q.pop();//取出队首
		
		if (book[node]) continue;//已加入最短路径集
		if (dist > b) return false;//不能到达
		else if (node == n) {//能够到达
			while (!p_q.empty()) p_q.pop();//不要忘记清空队列
			return true;
		}
		book[node] = true;//加入最短路径集

		int index = head[node];
		while (index != -1) {//尝试更新最短路径
			int v = edges[index].v;
			if (!book[v] && cost[v] <= limit) {//未加入最短路径集 && 不高于限制
				p_q.push(pair<int, int>(dist + edges[index].p, v));
			}
			index = edges[index].next;
		}
	}
	return false;//不能到达
}

//二分搜索
int l, r;
bool bin_search() {
	l = 0, r = NaN;
	bool is_find = false;
	while (l + 1 != r) {
		int middle = (l + r) / 2;
		if (dijkstra(middle)) {//能够到达
			r = middle;
			is_find = true;
		}
		else {//不能到达
			l = middle;
		}
	}
	return is_find;
}


int main() {
	memset(head + 1, -1, sizeof(int) * max_n);//初始化
	int u, v, p;
	cin >> n >> m >> b;
	for (int i = 1; i <= n; i++) cin >> cost[i];
	for (int i = 1; i <= m; i++) {//存图
		cin >> u >> v >> p;
		add_edge(u, v, p);
	}

	if (bin_search()) cout << r;
	else cout << "AFK";
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

WitheredSakura_

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

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

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

打赏作者

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

抵扣说明:

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

余额充值