题目传送门(内部题97)


输入格式

  第一行三个整数$n,m,p$,第二行$p$个整数$x_1\sim x_p$表示特殊点的编号。接下来$m$行每行三个整数$u,v,w$表示一条连接$u$和$v$,长度为$w$的边。


输出格式

  输出一行$p$个整数,第$i$个整数表示$x_i$的答案。


样例

样例输入:

5 6 3
2 4 5
1 2 4
1 3 1
1 4 1
1 5 4
2 3 1
3 4 3

样例输出:

3 3 5


数据范围与提示

  对于$10\%$的数据,$n,m\leqslant 50,000,p\leqslant 10$。
  对于$40\%$的数据,$n,m\leqslant 50,000$。
  对于另外$5\%$的数据,$p=n$。
  对于$100\%$的数据,$1\leqslant n,m\leqslant 2\times 10^5,2\leqslant p\leqslant n,1\leqslant x_i\leqslant n$,$x_i$互不相同,$1\leqslant u,v\leqslant n,1\leqslant w\leqslant 10^9$。


题解

把每个点都放入堆里,然后跑最短路。

对于源点$i$,由$i$拓展的点$j$以及与$j$相邻且不由$i$拓展的点$k$,如果$i$的最优路径从$j$走到了$k$,那么走到拓展$k$的源点是最优的。

然后枚举每一条边更新答案即可。

时间复杂度:$\Theta(m\log n)$。

期望得分:$100$分。

实际得分:$100$分。


代码时刻

#include<bits/stdc++.h>
using namespace std;
struct rec{int nxt,to;long long w;}e[500000];
int head[200001],cnt=1;
int n,m,p;
int t[200001];
long long dis[200001],ans[200001],f[200001];
bool vis[200001];
priority_queue<pair<long long,int>,vector<pair<long long,int>>,greater<pair<long long,int>>>q;
void add(int x,int y,long long w)
{
	e[++cnt].nxt=head[x];
	e[cnt].to=y;
	e[cnt].w=w;
	head[x]=cnt;
}
void Dij()
{
	while(q.size())
	{
		int x=q.top().second;
		q.pop();
		if(vis[x])continue;
		vis[x]=1;
		for(int i=head[x];i;i=e[i].nxt)
			if(dis[e[i].to]>dis[x]+e[i].w)
			{
				dis[e[i].to]=dis[x]+e[i].w;
				ans[e[i].to]=ans[x];
				q.push(make_pair(dis[e[i].to],e[i].to));
			}
	}
}
int main()
{
	memset(dis,0x3f,sizeof(dis));
	memset(f,0x3f,sizeof(f));
	scanf("%d%d%d",&n,&m,&p);
	for(int i=1;i<=p;i++)
	{
		scanf("%d",&t[i]);
		q.push(make_pair(0,t[i]));
		dis[t[i]]=0;ans[t[i]]=t[i];
	}
	for(int i=1;i<=m;i++)
	{
		int u,v;
		long long w;
		scanf("%d%d%lld",&u,&v,&w);
		add(u,v,w);add(v,u,w);
	}
	Dij();
	for(int i=2;i<=cnt;i+=2)
	{
		int x=ans[e[i].to];
		int y=ans[e[i^1].to];
		if(x==y)continue;
		f[x]=min(f[x],dis[e[i].to]+dis[e[i^1].to]+e[i].w);
		f[y]=min(f[y],dis[e[i].to]+dis[e[i^1].to]+e[i].w);
	}
	for(int i=1;i<=p;i++)printf("%lld ",f[t[i]]);
	return 0;
}

rp++

01-15 19:38