题解 | #传送门#
传送门
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<<' '; } }