题目背景
在艾泽拉斯大陆上有一位名叫歪嘴哦的神奇术士,他是部落的中坚力量。
有一天他醒来后发现自己居然到了联盟的主城暴风城。
在被众多联盟的士兵攻击后,他决定逃回自己的家乡奥格瑞玛。
题目描述
在艾泽拉斯,有 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 1≤ai,bi≤n)。表示城市 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 n≤200, m ≤ 1 0 4 m\leq 10^4 m≤104, b ≤ 200 b\leq 200 b≤200;
对于 100 % 100\% 100% 的数据,满足 n ≤ 1 0 4 n\leq 10^4 n≤104, m ≤ 5 × 1 0 4 m\leq 5\times 10^4 m≤5×104, b ≤ 1 0 9 b\leq 10^9 b≤109;
对于 100 % 100\% 100% 的数据,满足 c i ≤ 1 0 9 c_i\leq 10^9 ci≤109, f i ≤ 1 0 9 f_i\leq 10^9 fi≤109,可能有两条边连接着相同的城市。
解题思路:
这道题就是求解路径中最大点权的最小值,像是这种描述,十有八九是二分了
给出一张图,每个节点有各自的点权,每条边有各自的边权
我们希望有一条从1
到n
的路径,使其每个点的点权尽可能的小同时边权和不大于一定的值(血量)
已经知道题意了,接下来考虑如何实现
最简单的方式就是暴力搜索,枚举每一条能够从1
到n
的路径,找出其中最大点权值最小的即可,找不到路径就输出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;
}