题目链接:Path
这不是求k短路吗?那直接A算法。仔细一看起点不固定。那A算法是行不通了。
考虑BFS搜索。
这提和牛客多校的一道求第k小团的题很像(思想很像)。 Kth Minimum Clique可以先看看这道题。
回到题。我们先把所有边压入一个优先队列,优先队列里记录当前到达的点以及路径,每次取出路径最小的,第一次取出就是第一小,第二次取出就是第二小,第三次取出就是第三小。。。。。
然后再把取出点的所有出边压入队列。
注意:
这里我们只能保证当前取出的就是最小的,不能保证队列里的所有元素都是前k小。我们是从最小的开始bfs的,那么很显然每次取出的都是最小的。比当前元素大一点的肯定就是由队列里的某一个元素转移过去的。可以预见,队列里的某一个元素转移之后完全有可能比队列的另外某个元素更小。所以队列里的所有元素不是前k小。
可以肯定按照这种想法是没错的。只是时间上能否过得去就是个问题。牛客那题数据只是100,暴力就能过了。对于此题5e4的数据,肯定就不行了。当天写这个bfs的时候先是炸内存,后面又是tle。哎。。。。问题就出在我们每次取出点向外扩展的时候是对所有出边。那么如果出边都比较多。多次扩展之后优先队列肯定就承受不住了,而且时间上也不允许。
如何优化
我们每次扩展的时候,其实没必要扩展所有出边。
如上图的时候。先对所有出边从小到大排序,假设有我们当前取出了v点,并且这个点是由u扩展过来的。那么对于e2,e3,e4。显然扩展他们中最小的e2就可以了。除此之外呢,其实还有一个需要扩展的地方,那就是u向e1扩展。因为对于e3,e4的扩展是比e2扩展大的,所以是没有必要的(至少当前是不需要的),而对于e1这条边我们是不确定的,所以需要加入队列。
综上:
优先队列里记录当前结点,以及路径和,以及当前结点扩展到第几条边了。每次扩展当前点的出边中最小的一条,以及扩展到当前点的点的下一条边,就是图中比e0稍微大一点的e1。
AC_CODE
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 100 * 100 * 5 + 10;
typedef pair<ll, int>PII;
vector<PII>v[N];
int n, m, k, query[N];
ll ans[N];
struct node{
ll w;
int st,id;
bool operator<(const node a)const {
return w > a.w;
}
};
int main() {
//freopen("a.in","r",stdin);
int t; scanf("%d",&t);
while (t--) {
scanf("%d%d%d",&n,&m,&k);
for (int i = 1; i <= n; i++)v[i].clear();
for (int i = 1, x, y; i <= m; i++) {
ll w; scanf("%d%d%lld", &x, &y, &w);
v[x].push_back({w,y});
}
for (int i = 1; i <= n; i++)sort(v[i].begin(),v[i].end());
int mx = 0;
for (int i = 1; i <= k; i++) {
scanf("%d",query+i);
mx = max(mx,query[i]);
}
priority_queue<node>q;
for (int i = 1; i <= n; i++) {
if (v[i].size())q.push({v[i][0].first,i,0});
}
int cnt = 0;
while (true) {
ll w = q.top().w;
int st = q.top().st, id = q.top().id;
q.pop();
ans[++cnt] = w;
if (cnt == mx)break;
if (id+1 < (int)v[st].size())q.push({w-v[st][id].first+v[st][id+1].first,st,id+1});
int y = v[st][id].second;
if (v[y].size())q.push({w+v[y][0].first,y,0});
}
for (int i = 1; i <= k; i++)printf("%lld\n",ans[query[i]]);
}
return 0;
}