团体程序设计天梯赛-练习集-L3-028 森森旅游 (30 分)

好久没出去旅游啦!森森决定去 Z 省旅游一下。

Z 省有 n 座城市(从 1 到 n 编号)以及 m 条连接两座城市的有向旅行线路(例如自驾、长途汽车、火车、飞机、轮船等),每次经过一条旅行线路时都需要支付该线路的费用(但这个收费标准可能不止一种,例如车票跟机票一般不是一个价格)。

Z 省为了鼓励大家在省内多逛逛,推出了旅游金计划:在 i 号城市可以用 1 元现金兑换 ai​ 元旅游金(只要现金足够,可以无限次兑换)。城市间的交通即可以使用现金支付路费,也可以用旅游金支付。具体来说,当通过第 j 条旅行线路时,可以用 cj​ 元现金 dj​ 元旅游金支付路费。注意: 每次只能选择一种支付方式,不可同时使用现金和旅游金混合支付。但对于不同的线路,旅客可以自由选择不同的支付方式。

森森决定从 1 号城市出发,到 n 号城市去。他打算在出发前准备一些现金,并在途中的某个城市将剩余现金 全部 换成旅游金后继续旅游,直到到达 n 号城市为止。当然,他也可以选择在 1 号城市就兑换旅游金,或全部使用现金完成旅程。

Z 省政府会根据每个城市参与活动的情况调整汇率(即调整在某个城市 1 元现金能换多少旅游金)。现在你需要帮助森森计算一下,在每次调整之后最少需要携带多少现金才能完成他的旅程。

输入格式:

输入在第一行给出三个整数 n,m 与 q(1≤n≤105,1≤m≤2×105,1≤q≤105),依次表示城市的数量、旅行线路的数量以及汇率调整的次数。

接下来 m 行,每行给出四个整数 u,v,c 与 d(1≤u,v≤n,1≤c,d≤109),表示一条从 u 号城市通向 v 号城市的有向旅行线路。每次通过该线路需要支付 c 元现金或 d 元旅游金。数字间以空格分隔。输入保证从 1 号城市出发,一定可以通过若干条线路到达 n 号城市,但两城市间的旅行线路可能不止一条,对应不同的收费标准;也允许在城市内部游玩(即 u 和 v 相同)。

接下来的一行输入 n 个整数 a1​,a2​,⋯,an​(1≤ai​≤109),其中 ai​ 表示一开始在 i 号城市能用 1 元现金兑换 ai​ 个旅游金。数字间以空格分隔。

接下来 q 行描述汇率的调整。第 i 行输入两个整数 xi​ 与 ai′​(1≤xi​≤n,1≤ai′​≤109),表示第 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

样例解释:

对于第一次汇率调整,森森可以沿着 1→2→4→6 的线路旅行,并在 2 号城市兑换旅游金;

对于第二次汇率调整,森森可以沿着 1→2→3→4→6 的线路旅行,并在 3 号城市兑换旅游金;

对于第三次汇率调整,森森可以沿着 1→3→5→6 的线路旅行,并在 1 号城市兑换旅游金。

代码示例(含详细注释):

#include <algorithm>
#include <iostream>
#include <queue>
#include <set>
#include <vector>
#include <limits.h>
using namespace std;
typedef long long LL;
#define line pair<int, LL>  //c、d的最大值为10^9,定义为long long

struct node {
    int id;                                               //城市号
    LL s;                                                 //花费
    bool operator<(const node& _node) const {  //重载小于号,s大的优先级低
        return s > _node.s;
    }
};

const int N = 100000;  //城市数量的最大值
int n, m, q;           //城市数量、旅行线路的数量、汇率调整的次数
int u, v;              //一条从u号城市通向v号城市的有向旅行线路
LL c, d;               //每次通过该线路需要支付c元现金或d元旅游金
int xi;                //x_i号城市
LL ai;                 //汇率调整后,x_i号城市能用1元现金兑换a_i个旅游金
vector<line> c_spend[N + 5], d_spend[N + 5];  //分别储存使用现金和旅游金的每一条有向旅行线路和其花费金额
LL c_min_spend[N + 5], d_min_spend[N + 5];
// c_min_spend 存储使用现金从城市1出发,到达所有城市的最小花费累计和
// d_min_spend 存储使用旅游金从城市n出发,到达所有城市的最小花费累计和
LL d_exchange[N + 5];           //存储现金兑换旅游金的数目
LL tran[N + 5];                 //从城市1出发,在第i个城市将现金换成旅游金的情况下,所需要的总现金额
bool vis1[N + 5], vis2[N + 5];  //当前路径走过了则更改为ture,否则为false
priority_queue<node> Q;         //定义大顶堆
multiset<LL> MinSpend;          //路径耗费(具有重复元素)

void Dijskra(int c, vector<line> S[], LL MinS[], bool Vis[]) {  //当前城市号,使用现金和旅游金到达另一个城市的花费,到达所有城市的每一步的花费
    //fill函数可以为数组或者vector中的每个元素赋以相同的值,通常用于初始化!数组的效率往往比vector高, 使用assign函数只能对vector赋初值 ~所以当要对数组赋初值时可以使用fill函数。
    fill(MinS + 1, MinS + n + 1, LLONG_MAX);  //城市号从1开始
    MinS[c] = 0;
    Q.push(node{c, 0});
    while (!Q.empty()) {
        int cur = Q.top().id;
        Q.pop();
        if (Vis[cur])
            continue;
        Vis[cur] = true;
        for (unsigned int i = 0; i < S[cur].size(); i++) {
            int goal = S[cur][i].first;
            LL w = S[cur][i].second;
            if (MinS[goal] > MinS[cur] + w) {
                MinS[goal] = MinS[cur] + w;
                Q.push(node{goal, MinS[goal]});
            }
        }
    }
}

int main() {
    cin >> n >> m >> q;
    for (int i = 0; i < m; i++) {
        cin >> u >> v >> c >> d;
        c_spend[u].push_back({v, c});
        d_spend[v].push_back({u, d});
    }
    for (int i = 1; i <= n; i++) {  //在i号城市能用1元现金兑换d_exchange[i]个旅游金
        cin >> d_exchange[i];
    }
    //使用Dijskra算法求最小耗费路径
    Dijskra(1, c_spend, c_min_spend, vis1);  //现金
    Dijskra(n, d_spend, d_min_spend, vis2);  //旅游金
    for (int i = 1; i <= n; i++) {
        if (c_min_spend[i] == LLONG_MAX || d_min_spend[i] == LLONG_MAX)  //没有经过的路径
            continue;
        //使用从城市1到达第i个城市所需要的最小现金数 + 从第i个城市到城市n所需要的最小旅游金数所转换成的现金数量
        tran[i] = c_min_spend[i] + (d_min_spend[i] + d_exchange[i] - 1) / d_exchange[i];
        MinSpend.insert(tran[i]);
    }
    for (int i = 0; i < q; i++) {
        cin >> xi >> ai;
        if (!tran[xi] || d_exchange[xi] == ai)
            cout << *MinSpend.begin() << endl;
        else {
            MinSpend.erase(MinSpend.find(tran[xi]));
            d_exchange[xi] = ai;
            tran[xi] = c_min_spend[xi] + (d_min_spend[xi] + d_exchange[xi] - 1) / d_exchange[xi];
            MinSpend.insert(tran[xi]);
            cout << *MinSpend.begin() << endl;
        }
    }
    return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

月初XH

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

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

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

打赏作者

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

抵扣说明:

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

余额充值