题意
给定一张有 n n n 个节点的图,有 m m m 条有向边,第 i i i 条边为: a i → b i a_i \rarr b_i ai→bi,花费为 s i s_i si。初始有 p p p 个硬币。
每个点有一个点权
w
i
w_i
wi,当位于这个点时可以在此地进行表演,每次表演赚取
w
i
w_i
wi 个硬币。
问从
1
→
n
1 \rarr n
1→n 所需的最少表演次数是多少
思路
当走到一个无法通过的边时,我们肯定要进行表演,但是这样不是最优的,因为我们可以提前预见性地在 路径上 点权最大 的点把硬币赚够再过来。
这样子的话我们就可以采用欠费的思想,并记录到当前点的这条路径上点权最大的点为 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(nm⋅logn)
#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
*/