堆+贪心
超级钢琴的套路
先将每个点的出边按权值大小排序
维护一个四元组$(w, u, v, p)$,表示当前路径长度为$w$,当前边的起点是$u$,终点是$v$,这条边是$u$的出边中排序后的第$p$条边
每次出堆的路径加入答案,然后路径向外拓展,有两种情况,一种是加入$v$出边中最小的边,第二种是加入这条边的下一条边
然后输出答案即可
正确性:
对于这样的拓展方式,考虑错误的情况,如果存在一条路径比当前堆顶更优而没有加入堆中,这种情况不可能存在,因为这条路径可以由比他更优的路径拓展而来,如果这条路径没有被加入堆中,那么也就说明他的前继路径存在于堆中没有被选中,不合法,所以贪心策略正确。
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 5e4 + 5; int n, m, Q; ll ans[maxn]; vector<pair<int, int> > G[maxn]; struct data { ll w; int u, v, p; data() {} data(ll _w, int _u, int _v, int _p) : w(_w), u(_u), v(_v), p(_p) {} bool friend operator < (const data &a, const data &b) { return a.w > b.w; } }; int main() { int T; scanf("%d", &T); while(T--) { scanf("%d%d%d", &n, &m, &Q); memset(ans, 0, sizeof(ans)); for(int i = 1; i <= n; ++i) G[i].clear(); priority_queue<data> q; for(int i = 1; i <= m; ++i) { int u, v, w; scanf("%d%d%d", &u, &v, &w); G[u].push_back(make_pair(w, v)); } for(int i = 1; i <= n; ++i) sort(G[i].begin(), G[i].end()); for(int i = 1; i <= n; ++i) if(!G[i].empty()) q.push(data(G[i][0].first, i, G[i][0].second, 0)); for(int _ = 1; _ <= 50000 && !q.empty(); ++_) { data t = q.top(); q.pop(); ans[_] = t.w; int u = t.u, v = t.v, p = t.p; if(G[v].size()) q.push(data(t.w + G[v][0].first, v, G[v][0].second, 0)); if(p != (int)G[u].size() - 1) q.push(data(t.w + G[u][p + 1].first - G[u][p].first, u, G[u][p + 1].second, p + 1)); } while(Q--) { int k; scanf("%d", &k); printf("%lld ", ans[k]); } } return 0; }