[2021CCCC天梯赛] L3-1 森森旅游(30分)
[2021CCCC天梯赛] L3-1 森森旅游(30分)
题目描述
好久没出去旅游啦!森森决定去 Z 省旅游一下。
Z 省有 n 座城市(从 1 到 n 编号)以及 m 条连接两座城市的有向旅行线路(例如自驾、长途汽车、火车、飞机、轮船等),每次经过一条旅行线路时都需要支付该线路的费用(但这个收费标准可能不止一种,例如车票跟机票一般不是一个价格)。
Z 省为了鼓励大家在省内多逛逛,推出了旅游金计划:在 i 号城市可以用 1 元现金兑换 \(a_i\) 元旅游金(只要现金足够,可以无限次兑换)。城市间的交通即可以使用现金支付路费,也可以用旅游金支付。具体来说,当通过第 j 条旅行线路时,可以用 元\(c_j\)现金或\(d_j\)元旅游金支付路费。注意: 每次只能选择一种支付方式,不可同时使用现金和旅游金混合支付。但对于不同的线路,旅客可以自由选择不同的支付方式。
森森决定从 1 号城市出发,到 n 号城市去。他打算在出发前准备一些现金,并在途中的某个城市将剩余现金 全部 换成旅游金后继续旅游,直到到达 n 号城市为止。当然,他也可以选择在 1 号城市就兑换旅游金,或全部使用现金完成旅程。
Z 省政府会根据每个城市参与活动的情况调整汇率(即调整在某个城市 1 元现金能换多少旅游金)。现在你需要帮助森森计算一下,在每次调整之后最少需要携带多少现金才能完成他的旅程。
输入格式:
输入在第一行给出三个整数 n,m 与 q(1≤n≤105,1≤m≤2×105,1≤q≤10^5),依次表示城市的数量、旅行线路的数量以及汇率调整的次数。
接下来 m 行,每行给出四个整数 u,v,c 与 d(1≤u,v≤n,1≤c,d≤10^9),表示一条从 u 号城市通向 v 号城市的有向旅行线路。每次通过该线路需要支付 c 元现金或 d 元旅游金。数字间以空格分隔。输入保证从 1 号城市出发,一定可以通过若干条线路到达 n 号城市,但两城市间的旅行线路可能不止一条,对应不同的收费标准;也允许在城市内部游玩(即 u 和 v 相同)。
接下来的一行输入 n 个整数 a1,a2,⋯,an(1≤ai≤10^9),其中 ai 表示一开始在 i 号城市能用 1 元现金兑换 ai个旅游金。数字间以空格分隔。
接下来 q 行描述汇率的调整。第 i 行输入两个整数 xi 与 ai′(1≤xi≤n,1≤ai′≤10^9),表示第 i 次汇率调整后,xi 号城市能用 1 元现金兑换 ai′ 个旅游金,而其它城市旅游金汇率不变。请注意:每次汇率调整都是在上一次汇率调整的基础上进行的。
输出格式:
对每一次汇率调整,在对应的一行中输出调整后森森至少需要准备多少现金,才能按他的计划从 1 号城市旅行到 n 号城市。
再次提醒:如果森森决定在途中的某个城市兑换旅游金,那么他必须将剩余现金全部、一次性兑换,剩下的旅途将完全使用旅游金支付。
输入样例:
6 11 3
1 2 3 5
1 3 8 4
2 4 4 6
3 1 8 6
1 3 10 8
2 3 2 8
3 4 5 3
3 5 10 7
3 3 2 3
4 6 10 12
5 6 10 6
3 4 5 2 5 100
1 2
2 1
1 17
输出样例:
8
8
1
思路
反图 最短路
考虑最终的答案:在第\(i\)个城市兑换旅游金,则需准备的现金为$$dis1[i]+\lceil \frac{dis2[i]}{a[i]} \rceil$$
式中,\(dis1[i]\)表示从\(1\)到\(i\)的现金花费,\(dis2[i]\)表示从\(i\)到\(n\)需花费的旅游金,\(a[i]\)为城市\(i\)的汇率。
\(dis1\)、\(dis2\)分别在原图和反图上跑一次dijkstra即可求出。求最少现金,只需要枚举中转点\(i\),对上式求最小值即可。
后续\(q\)次修改,都是只修改\(a\)数组的一个点,对\(dis1\)、\(dis2\)没有影响,故每次相当于只修改了一个城市的花费,查询所有城市花费的最小值,即每次单点修改,区间求最值。可用线段树、堆(priority_queue)或平衡树(multiset)维护。
本题的坑点:只保证从\(1\)到\(n\)存在路径,不保证图联通。
Code
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5, M = 2e5 + 5;
typedef long long ll;
typedef pair<ll, int> node;
struct Graph {
int etot = 0, head[N] = {0}, ver[M] = {0}, nxt[M] = {0}, val[M] = {0};
void add(int u, int v, int w) {
ver[++etot] = v;
val[etot] = w;
nxt[etot] = head[u];
head[u] = etot;
}
ll dis[N] = {0};
bool vis[N] = {0};
void Dijkstra(int st) {
priority_queue< node, vector<node>, greater<node> >q;
memset(dis, 0x3f, sizeof(dis));
q.emplace(0, st);
dis[st] = 0;
while (!q.empty()) {
int u = q.top().second;
q.pop();
if (vis[u]) continue;
vis[u] = true;
for (int i = head[u]; i; i = nxt[i]) {
int v = ver[i];
if (dis[u] + val[i] < dis[v]) {
dis[v] = dis[u] + val[i];
q.emplace(dis[v], v);
}
}
}
}
} G1, G2;
int n, m, Q;
ll a[N];
int main() {
scanf("%d%d%d", &n, &m, &Q);
for (int i = 1, u, v, c, d; i <= m; ++i) {
scanf("%d%d%d%d", &u, &v, &c, &d);
G1.add(u, v, c);
G2.add(v, u, d);
}
G1.Dijkstra(1);
G2.Dijkstra(n);
multiset<ll> Set;
for (int i = 1; i <= n; ++i) {
scanf("%lld", &a[i]);
if (G1.vis[i] && G2.vis[i]) Set.insert(G1.dis[i] + ceil((double)G2.dis[i] / a[i]));
}
for (int i = 1, x, y; i <= Q; ++i) {
scanf("%d%d", &x, &y);
if (G1.vis[x] && G2.vis[x]) {
Set.erase(Set.find(G1.dis[x] + ceil((double)G2.dis[x] / a[x])));
a[x] = y;
Set.insert(G1.dis[x] + ceil((double)G2.dis[x] / a[x]));
}
printf("%lld\n", *Set.begin());
}
return 0;
}