把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

题解 P6880 [JOI 2020 Final] オリンピックバス

洛谷

题意

应该显然,注意最多只能翻转一条边,并且可以不翻转。

分析

首先观察数据范围 2N2001M5×104,可以发现我们的 NM 并不是同级的,因此,在众多的最短路算法中,我们应当选择不加堆优化的 dijkstra 算法,并且使用邻接矩阵,这是 O(n2) 的时间复杂度,加入使用了堆优化或者使用邻接表,这就会使我们的最短路的时间变大。

接下来就是这道题比较巧妙的地方了,我们都知道 M1000 的暴力怎么打,使用不加堆优化的迪杰,加上枚举翻转的边,时间复杂度是 O(M×N2)。但是这 m 次最短路并不是都是必须的,首先,假如我们枚举的这条边并不在 1nn1 的最短路当中,那么翻转场此边并不会影响原来的路径的大小,但是我们的最短路可能会因此发生改变,不过只需要用原来 1v 的最短路加上 un 的最短路加上翻转的边,与原最短路取小值即可。

那么在原最短路径的边呢,没有什么高大上的算法,直接和暴力一样即可。

接下来分析一下时间复杂度,我们的暴力是 O(n2),不在最短路上的取答案是 O(1),而最短路径上的边数,最多只有 O(n)

注意,这里的最短路径上的边,是指确定的一条路径,这条路径可以用记转移来的结点,也就是最短路径树解决,而不是可能出现在最短路径上的边。

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e2+5;
const int M = 5e4+5;
const int INF = 0x3f3f3f3f3f3f;
inline int read() {
int x;
scanf("%lld",&x);
return x;
}
int n, m;
struct Edge {
int u,v,c,d;
} edge[M];
int lj[N][N],g[N][N],mark[N],dis[5][N],fa[5][N],vis[2][N][N];
//dis[0] 1 正 dis[1] n 正 dis[2] 1 反 dis[3] n 反 dis[4] ls
inline void dj(int st,int *dis,int *fa) {
for(int i=1; i<=n; ++i) dis[0]=dis[i]=INF,mark[i]=fa[i]=0;
dis[st]=0;
while(1) {
int now=0;
for(int i=1; i<=n; ++i) if(!mark[i]&&dis[i]<dis[now]) now=i;
if(!now) break;
mark[now]=1;
for(int i=1; i<=n; ++i) if(dis[i]>dis[now]+lj[now][i]) dis[i]=dis[now]+lj[now][i],fa[i]=now;
}
return ;
}
signed main() {
n=read(),m=read();
for(int i=1; i<=m; ++i) edge[i]=<%read(),read(),read(),read()%>;
memset(lj,0x3f,sizeof lj),memset(g,0x3f,sizeof g);
for(int i=1; i<=m; ++i) {
int u=edge[i].u,v=edge[i].v,c=edge[i].c;
g[u][v]=min(g[u][v],c);
if(g[u][v]<lj[u][v]) swap(g[u][v],lj[u][v]);
}
dj(1,dis[0],fa[0]),dj(n,dis[1],fa[1]);
for(int i=1; i<=n; ++i) for(int j=i; j<=n; ++j) swap(lj[i][j],lj[j][i]);
dj(1,dis[2],fa[2]),dj(n,dis[3],fa[3]);
for(int i=1; i<=n; ++i) for(int j=i; j<=n; ++j) swap(lj[i][j],lj[j][i]);
int res=dis[0][n]+dis[1][1];
if(dis[0][n]<INF) for(int now=n; now; now=fa[0][now]) vis[0][fa[0][now]][now]=1;
if(dis[1][1]<INF) for(int now=1; now; now=fa[1][now]) vis[1][fa[1][now]][now]=1;
for(int i=1; i<=m; ++i) {
int u=edge[i].u,v=edge[i].v,c=edge[i].c,d=edge[i].d;
int len1=lj[u][v],len2=lj[v][u];
if(lj[u][v]==c) lj[u][v]=g[u][v];
lj[v][u]=min(lj[v][u],c);
int tot=d;
if(vis[0][u][v]) dj(1,dis[4],fa[4]),tot+=dis[4][n];
else tot+=min(dis[0][n],dis[0][v]+dis[3][u]+c);
if(vis[1][u][v]) dj(n,dis[4],fa[4]),tot+=dis[4][1];
else tot+=min(dis[1][1],dis[1][v]+dis[2][u]+c);
res=min(res,tot);
lj[u][v]=len1,lj[v][u]=len2;
}
cout<<(res>=INF?-1:res)<<"\n";
return 0;
}

总结起来,这道题利用了最短路的边数小,以及不加堆优化的 dijkstra,这两个平时并不多用的性质,是一道最短路好题。

posted @   djh0314  阅读(28)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
浏览器标题切换
浏览器标题切换end
点击右上角即可分享
微信分享提示