题解 P6464 【传送门】

· · 题解

朋友们好啊!

既然题目叫做传送门,那我们先放一个传送门

题目传送门 P6464 传送门

题目就不过多阐释了,这是一道非常有趣的最短路问题

先考虑一下数据范围。哇,n≤100诶,然后我啪就打了一个非常暴力的Floyd,很快啊!

这里先说明一下:

$g[i][j]$表示有了传送门之后$i$到$j$的最短路。 主要内容如下: ```cpp for (int k=1;k<=n;k++)//标准Floyd for (int i=1;i<=n;i++) for (int j=1;j<=n;j++) f[i][j]=min(f[i][j],f[i][k]+f[k][j]); ans=1e9; for (int k=1;k<=n;k++) for (int l=k+1;l<=n;l++) { for (int i=1;i<=n;i++) for (int j=1;j<=n;j++) g[i][j]=f[i][j]; g[k][l]=g[l][k]=0; for (int m=1;m<=n;m++)//每更新一次传送门就跑一次Floyd for (int i=1;i<=n;i++) for (int j=1;j<=n;j++) g[i][j]=min(g[i][j],g[i][m]+g[m][j]); sum=0; for (int i=1;i<=n;i++) for (int j=i+1;j<=n;j++) sum+=g[i][j]; ans=min(ans,sum); } ``` 这段代码时间复杂度为$O(n^5)$,这是肯定要爆的,说明需要优化将复杂度优化为$O(n^4)$。那么该如何优化呢? 我们发现在每次更改传送门后不断在重复做$Floyd$,每个点都要当作一次中转点$m$,效率低下。既然每一次变化只改变了$k$和$l$之间的距离,我们只需要分别将$k$和$l$作为中转点来做$Floyd$。于是复杂度直接降为$O(n^4)$,完全可以AC了。 具体内容如下: ```cpp for (int k=1;k<=n;k++) for (int l=k+1;l<=n;l++) { for (int i=1;i<=n;i++) for (int j=1;j<=n;j++) g[i][j]=f[i][j]; g[k][l]=g[l][k]=0; for (int i=1;i<=n;i++) for (int j=1;j<=n;j++) g[i][j]=min(g[i][j],g[i][k]+g[k][j]); for (int i=1;i<=n;i++) for (int j=1;j<=n;j++) g[i][j]=min(g[i][j],g[i][l]+g[l][j]); sum=0; for (int i=1;i<=n;i++) for (int j=i+1;j<=n;j++) sum+=g[i][j]; ans=min(ans,sum); } ``` 完整代码(AC Code) ```cpp #include<bits/stdc++.h> using namespace std; int n,m,sum,ans; int f[110][110],g[110][110]; int main() { cin>>n>>m; for (int i=1;i<=n;i++)//初始化 for (int j=1;j<=n;j++) if (i==j) f[i][j]=0; else f[i][j]=1e9;//最大值记得开大一点 for (int i=1;i<=m;i++) { int u,v,w; cin>>u>>v>>w; f[u][v]=f[v][u]=w;//双向建边 } for (int k=1;k<=n;k++)//无传送门时的最短路f数组 for (int i=1;i<=n;i++) for (int j=1;j<=n;j++) f[i][j]=min(f[i][j],f[i][k]+f[k][j]); ans=1e9; for (int k=1;k<=n;k++) for (int l=k+1;l<=n;l++) { for (int i=1;i<=n;i++)//注意:每次更新传送门都要重置g数组 for (int j=1;j<=n;j++) g[i][j]=f[i][j]; g[k][l]=g[l][k]=0; for (int i=1;i<=n;i++)//k作为中转点 for (int j=1;j<=n;j++) g[i][j]=min(g[i][j],g[i][k]+g[k][j]); for (int i=1;i<=n;i++)//l作为中转点 for (int j=1;j<=n;j++) g[i][j]=min(g[i][j],g[i][l]+g[l][j]); sum=0; for (int i=1;i<=n;i++)//求边和 for (int j=i+1;j<=n;j++) sum+=g[i][j]; ans=min(ans,sum);//更新最小值 } cout<<ans<<endl; return 0; } ``` 好了,这道题就到这儿了(点到为止) 谢谢朋友们!