题目
代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
typedef pair<int,int> PII;
const int N = 100010;
const int M = 3*N;
int n,m;
int h[N], e[M], w[M], ne[M], idx;
int dist[N];
bool st[N];
void add(int a, int b, int c)
{
e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
void dijkstra()
{
priority_queue<PII,vector<PII>,greater<PII>> heap;//greater代表从小到大排序
heap.push({0,0});//heap.first是从虚拟原点到heap.second的最短距离
memset(dist, 0x3f, sizeof dist);
dist[0]=0;
while(heap.size())
{
auto t=heap.top();
heap.pop();
int ver=t.second;
if(st[ver]) continue;
st[ver]=true;//点ver的st为true表示已经用过这个点去更新dist
for(int i=h[ver]; ~i; i=ne[i])//i=h[ver],i为ver邻接的边的编号
{
int j=e[i];//j是这条与ver邻接的边的另一个点
if(dist[j]>dist[ver]+w[i])
{
//从虚拟原点到点j的距离如果大于从虚拟原点到点ver的距离再加上从ver到j的边的权值
//就修改dist[j],并且把{dist[j],j}入队
dist[j]=dist[ver]+w[i];
heap.push({dist[j],j});
}
}
}
//实际上就是定义一个优先队列,按点的编号从小到大排序
//先更新原点与其相邻的点之间的距离,然后把更新的点入队,后面再重复出队+入队的流程,不断更新dist
}
int main()
{
cin>>n>>m;
memset(h, -1, sizeof h);
while(m--)
{
int a, b, c;
cin>>a>>b>>c;
add(a, b, c);
add(b, a, c);
}
int k;
cin>>k;
while(k--)
{
int x;
cin>>x;
add(0,x,0);//从虚拟原点到商店的边
}
dijkstra();
int q;
cin>>q;
while(q--)
{
int x;
cin>>x;
cout<<dist[x]<<endl;
}
return 0;
}
/*
由于数据量比较大,如果对每一个输入都进行一次djikstra去搜索最近的商店就会tle
构造虚拟原点,在虚拟原点与商店之间加入一条权值为0的边
进行一次djikstra,从虚拟原点到各个点之间的距离就是从各个点到最近的商店的距离
问题1:为什么添加虚拟原点?
添加虚拟原点后就变成了单源最短路径,时间更快。
问题2:为什么在虚拟原点与商店之间加入边而不是虚拟原点与要求的求最短距离的点?
因为前者的dist[j]对应的路径一定是经过某个商店并且到点j的最短路径
但是后者dist[j]对应的路径是从虚拟原点到商店j的路径,而且无法确定是否经过规定的i点
如果要确定是否经过i点还需要记录path,比较麻烦。
问题3:如何保证这样就是最短距离?
原问题本来是求每个商店到规定点的距离,然后选取一个最小的,属于多源最短路
那么问题的解的集合就可以分为从1、2...k这些商店中到规定点的最短距离的子集
每个子集代表到某点j的最短路径是从商店k出发的
如果加入虚拟原点,先建立虚拟原点到各个商店的权值为0的路径
实际上解也可以分为从虚拟原点经过1、2...k商店到规定点的距离的子集
由于虚拟原点到各个商店的路径权值为0,从商店到某点最短距离的解与多源最短路的情况是一样的
因此这两种情况下的集合及其子集的划分是一样的,并且其中的元素是一一对应的
*/