Codeforces Round 857 F. The way home 【Dijkstra 欠费】

F

题意

给定一张有 n n n 个节点的图,有 m m m 条有向边,第 i i i 条边为: a i → b i a_i \rarr b_i aibi,花费为 s i s_i si。初始有 p p p 个硬币。

每个点有一个点权 w i w_i wi,当位于这个点时可以在此地进行表演,每次表演赚取 w i w_i wi 个硬币。
问从 1 → n 1 \rarr n 1n 所需的最少表演次数是多少

思路

当走到一个无法通过的边时,我们肯定要进行表演,但是这样不是最优的,因为我们可以提前预见性地在 路径上 点权最大 的点把硬币赚够再过来。

这样子的话我们就可以采用欠费的思想,并记录到当前点的这条路径上点权最大的点为 b e s t best best,当需要表演时在 b e s t best best 表演,并添加相应的表演次数到总次数中。

这样子我们就可以有一个状态: d p [ u ] [ b e s t ] dp[u][best] dp[u][best] 表示当前节点为 u u u,到 u u u 的某条路径上的最大点权点为 b e s t best best最小表演次数 以及 最大剩余硬币数,不难证明这个状态是 无后效性 的,不会走回头路

那么我们只需要在此基础上跑 D i j k s t r a Dijkstra Dijkstra 即可

时间复杂度: O ( n m ⋅ log ⁡ n ) O(nm \cdot \log n) O(nmlogn)

#include<bits/stdc++.h>
#define fore(i,l,r)	for(int i=(int)(l);i<(int)(r);++i)
#define fi first
#define se second
#define endl '\n'
#define ull unsigned long long
#define ALL(v) v.begin(), v.end()
#define Debug(x, ed) std::cerr << #x << " = " << x << ed;

const int INF=0x3f3f3f3f;
const long long INFLL=1e18;

typedef long long ll;

const int N = 805;

std::vector<std::pair<int, ll>> g[N];
ll w[N];

struct node{
    ll show;  //表演次数
    ll money; //剩余最大金额
    int best; //路径上点权最大的顶点
    int u; //当前顶点

    bool operator < (const node& nd) const{
        if(show != nd.show) return show > nd.show;
        if(money != nd.money) return money < nd.money;
        return w[best] < w[nd.best];
    }
};

int main(){
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);
    int t;
    std::cin >> t;
    while(t--){
        int n, m, p;
        std::cin >> n >> m >> p;
        fore(i, 1, n + 1){
            std::cin >> w[i];
            g[i].clear();
        }
        while(m--){
            int u, v, s;
            std::cin >> u >> v >> s;
            g[u].push_back({v, s});
        }

        std::vector<std::vector<std::pair<ll, ll>>> dp(n + 1, std::vector<std::pair<ll, ll>>(n + 1, {INFLL, 0}));
        dp[1][1] = {0, p};
        std::priority_queue<node> q;
        q.push({0, p, 1, 1});
        while(!q.empty()){
            auto [show, money, best, u] = q.top();
            q.pop();
            if(show > dp[u][best].fi || money < dp[u][best].se) continue;
            for(auto [v, s] : g[u]){
                int nbest = best;
                ll nmoney = money, nshow = show;
                if(w[v] > w[best]) nbest = v;
                if(money < s){ //剩下的钱不够通过这条边
                    nshow += (s - money + w[best] - 1) / w[best];
                    nmoney += (nshow - show) * w[best];
                }
                nmoney -= s;
                if(nshow < dp[v][nbest].fi || (nshow == dp[v][nbest].fi && nmoney > dp[v][nbest].se)){
                    dp[v][nbest] = {nshow, nmoney};
                    q.push({nshow, nmoney, nbest, v});
                }
            }
        }

        ll ans = INFLL;
        fore(i, 1, n + 1) ans = std::min(ans, dp[n][i].fi);
        std::cout << (ans == INFLL ? -1 : ans) << endl;
    }
    return 0;
}


/*
https://codeforces.com/contest/1802/problem/F
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值