题目背景
在艾泽拉斯大陆上有一位名叫歪嘴哦的神奇术士,他是部落的中坚力量。
有一天他醒来后发现自己居然到了联盟的主城暴风城。
在被众多联盟的士兵攻击后,他决定逃回自己的家乡奥格瑞玛。
题目描述
在艾泽拉斯,有 n 个城市。编号为 1,2,3,…,n。
城市之间有 m 条双向的公路,连接着两个城市,从某个城市到另一个城市,会遭到联盟的攻击,进而损失一定的血量。
每次经过一个城市,都会被收取一定的过路费(包括起点和终点)。路上并没有收费站。
假设 1 为暴风城,n 为奥格瑞玛,而他的血量最多为 b,出发时他的血量是满的。如果他的血量降低至负数,则他就无法到达奥格瑞玛。
歪嘴哦不希望花很多钱,他想知道,在所有可以到达奥格瑞玛的道路中,对于每条道路所经过的城市收费的最大值,最小值为多少。
输入格式
第一行 3 个正整数,n,m,b。分别表示有 n 个城市,m 条公路,歪嘴哦的血量为 b。
接下来有 n 行,每行 1 个正整数,fi。表示经过城市 i,需要交费 fi 元。
再接下来有 m 行,每行 3 个正整数,ai,bi,ci(1≤ai,bi≤n)。表示城市 ai 和城市 bi 之间有一条公路,如果从城市 ai 到城市 bi,或者从城市 bi 到城市 ai,会损失 ci 的血量。
输出格式
仅一个整数,表示歪嘴哦交费最多的一次的最小值。
如果他无法到达奥格瑞玛,输出 AFK。
思路:二分答案、最短路
二分答案的基本原理
问题转化
所求为交费,所以对交费进行二分答案。
将伤害视为边权,交费视为点权,将高交费的点舍去。
把路径长度看为边权和,方便处理血量。
二分
对于每个 money:
- 在遍历边时,需要额外判断目标点的点权是否大于 money,若是则跳过。
- 如果起始点的点权已经大于 money,直接结束判断。
- 每次判断时,应该初始化。
- 对于边权相关的量,使用
long long类型存储(防止溢出)。
注意处理二分边界。
注意无向图需要开二倍空间(来回两条边)。
24/10/02 补充:
之所以二分可行,是因为给定了一个 money,有以下关系:
- 比 money 小的不一定能通过,但是更优。
- 比 money 大的一定能通过,但是更差。
也就是说,最优解一定小于等于 money。
因此,二分可行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
| #include <bits/stdc++.h>
using namespace std; using ll = long long;
template <typename T> T d(T x){ cout << x << endl; return x; }
const int inf = 0x7f7f7f7f;
const int N = 1e4 + 10; const int M = 5e4 * 2 + 10; const int MAX = 1e9 + 10;
int cost[N];
int n, m; ll health;
struct edge { int to, damage, next; } edges[M];
int head[N]; int pt = 1;
bool vis[N]; ll dist[N];
struct node { int from; ll damage; bool operator<(const node &n) const { return damage > n.damage; } }; priority_queue<node> q;
void add_edge(int from, int to, int damage) { edge &e = edges[pt]; e.to = to; e.damage = damage; e.next = head[from]; head[from] = pt++; }
void init() { memset(dist, inf, sizeof dist); memset(vis, 0, sizeof vis); dist[1] = 0ll; q = priority_queue<node>(); }
bool check(int money) { if(money < cost[1]) return false; init(); q.push({1, 0}); while((q.size())) { node n = q.top(); q.pop(); int from = n.from; if(vis[from]) continue; vis[from] = true; for(int i = head[from]; i; i = edges[i].next) { edge e = edges[i]; if(cost[e.to] > money) continue; if(vis[e.to]) continue; if(dist[e.to] > dist[from] + e.damage){ dist[e.to] = dist[from] + e.damage; q.push({e.to, dist[e.to]}); } } } return ((dist[n]) <= (health)); }
int get_ans() { int left = 1, right = MAX; int mid = (left + right) >> 1; while(left < right) { bool flag = check(mid); if(flag) { right = mid; } else { left = mid + 1; } mid = (left + right) >> 1; } return mid; }
int main() { cin >> n >> m >> health; for(int i = 1; i <= n; i++) { cin >> cost[i]; } int t1, t2, t3; for(int j = 1; j <= m; j++) { cin >> t1 >> t2 >> t3; add_edge(t1, t2, t3); add_edge(t2, t1, t3); }
if((!check(MAX))) cout << "AFK" << endl; else cout << get_ans() << endl;
return 0; }
|
结语
算是独属于本题的特殊环节。
这道题的坑很多,我也自己创造了很多坑(看提交记录就能看出来)。
主要是以下问题:
- 没关
freopen(之所以开这个是因为测试数据的时候嫌控制台粘贴太慢了)。
- 二分判断失误。
- 不小心把退出条件写成了
cost[e.to] >= money(应不含等于号)。
总之,这题虽然看着简单,但是实际操作会有很多的坑点需要注意。