题解 | #传送门#
传送门
https://ac.nowcoder.com/acm/problem/236210
这道题就是跑两遍堆优化版的Dijkstra,从1和n开始记录他们到每一个点的距离用dist1[]和dist2[]来记录。
最重要的是传送门该怎么样去存储:比如传送门编号为i,里面有a,b,c三个点,可以用a点去找到传送门编号,再从传送门编号里面去找对应的传送门。
比如 i 号传送门进入a点,那就add(a,i,t)和add(i,a,t)来存储数据,从a到i的一条边和从i到a的一条边,找到i号传送门编号,就可以找到其他对应的传送门,但会发现从a走到b要先走到 i 再走到b到堆优化里面就相当于是d[ b ] = d[ i ] + w [ a ]所以这是两倍距离,从b到c也是一样,只需要最后将结果就是实际距离的两倍。但会出现 i = 1和a = 1 的情况(举例),会出现两条边重合的现象,所以i要加上100000(传送门总数量)来将重边分开。
#include<bits/stdc++.h>
using namespace std;
const int N=200010,M=2000010;
typedef pair<int,int> PII;
int h[N],e[M],ne[M],w[M],idx=0;
int dist1[N],dist2[N];
int st[N];
int n,m;
void add(int a,int b,int c) //邻接表存数据
{
e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
void Dijkstra(int x,int *d)
{
memset(st,0,sizeof(st));
priority_queue<PII,vector<PII>,greater<PII>>q; //这里一定要用PII存两个数据,只存节点会报错,有知道的可以评论里面说一说,我也不知道
q.push({0,x});
d[x]=0;
while(q.size()){
auto num=q.top();q.pop();
int t=num.second;
if(st[t])continue;
st[t]=1;
for(int i=h[t];i!=-1;i=ne[i])
{
int j=e[i];
if(d[j]>d[t]+w[i]){
d[j]=d[t]+w[i];
q.push({d[j],j});
}
}
}
}
int main()
{
memset(dist1,0x3f,sizeof dist1);
memset(dist2,0x3f,sizeof dist2);
memset(h,-1,sizeof h);
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
int t,s;
scanf("%d%d",&t,&s);
int r;
for(int j=1;j<=s;j++)
{
scanf("%d",&r);
add(100000+i,r,t); //记录从传送门类型为i的传送门到r的时间
add(r,100000+i,t); //记录从传送门r到传送门类型i的时间 ,,因为会有重边所以加上传送门数量排重。
//就相当于在两个同类型的传送门之间加了一个额外的点,传送门类型,距离也就翻倍了
}
}
Dijkstra(1,dist1);
Dijkstra(n,dist2); //两遍DJ求dist1和dist2。
int ans=0x3f3f3f3f;
for(int i=1;i<=n;i++)
if(dist1[i]!=0x3f3f3f3f&&dist2[i]!=0x3f3f3f3f)
ans=min(ans,max(dist1[i],dist2[i])); //一个人走到某一个点可以停下来等另一个人,所以只需要得到两个人中最晚到这个点的时间,主意是两倍
if(ans==0x3f3f3f3f) //如果ans没更新那就是没有走到相同的点,输出-1.
cout<<-1<<endl;
else
{
cout<<ans/2<<endl; //因为是两倍所以要除2.
for(int i=1;i<=n;i++)
if(max(dist1[i],dist2[i])==ans) //在所有点中找是否有和上面一样时间的点,从1开始,字典序输出
cout<<i<<' ';
}
}
查看10道真题和解析