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

\(\text{Problem}:\)[JOI 2020 Final] オリンピックバス

\(\text{Solution}:\)

首先有一个朴素的想法:\(1\rightarrow n\) 的最短路和 \(n\rightarrow 1\) 的最短路可以分别计算。枚举每一条边,翻转之后跑最短路。时间复杂度是 \(O(n^{2}m)\)

考虑优化这个算法。预处理 \(i\rightarrow j\) 在原图上的最短路,记为 \(dis_{i,j}\)。以求 \(1\rightarrow n\) 的最短路为例,枚举每一条边:

  • 这条边不在 \(1\rightarrow n\) 的最短路上,那么翻转这条边不会对 \(1\rightarrow i(1\leq i \leq n)\) 的最短路产生影响。故此时 \(1\rightarrow n\) 的答案为 \(\min\{dis_{1,n},dis_{1,v_{i}}+c_{i}+dis_{u_{i},n}\}\)
  • 这条边在 \(1\rightarrow n\) 的最短路上,翻转这条边,并重新求出 \(1\rightarrow n\) 的最短路。

实际上,这个算法的复杂度还是 \(O(n^{2}m)\)。考虑对于一个点 \(x\),如果同时存在多个 \(y\) \(dis_{1,y}+c_{y,x}=dis_{1,x}\),则我们只需要计算其中一个 \(y\)。具体的,可以记 \(pre_{i,j}\) 表示在原图上,以 \(i\) 为起点的最短路中,转移到 \(j\) 的上一个节点。这样等于在原图上建出一个最短路 \(DAG\),且它的边数是 \(O(n)\) 的,于是时间复杂度降为 \(O(n^{3}+m)\),可以通过本题。

\(\text{Code}:\)

#include <bits/stdc++.h>
#pragma GCC optimize(3)
#define int long long
#define ri register
#define mk make_pair
#define fi first
#define se second
#define pb push_back
#define eb emplace_back
#define is insert
#define es erase
using namespace std; const int N=210, M=50010, INF=1e18;
inline int read()
{
	int s=0, w=1; ri char ch=getchar();
	while(ch<'0'||ch>'9') { if(ch=='-') w=-1; ch=getchar(); }
	while(ch>='0'&&ch<='9') s=(s<<3)+(s<<1)+(ch^48), ch=getchar();
	return s*w;
}
int n,m,U[M],V[M],C[M],D[M],W[N][N],f[N][N],dis[N][N],pre[N][N],book[N],Ans,Dis[N];
inline void Dijk(int S)
{
	for(ri int i=0;i<=n;i++) dis[S][i]=INF;
	memset(book,0,sizeof(book));
	dis[S][S]=0;
	while(1)
	{
		int x=0;
		for(ri int i=1;i<=n;i++) if(!book[i] && dis[S][i]<dis[S][x]) x=i;
		if(!x) break;
		book[x]=1;
		for(ri int i=1;i<=n;i++) if(dis[S][i]>dis[S][x]+W[x][i]) dis[S][i]=dis[S][x]+W[x][i], pre[S][i]=x;
	}
}
inline void Tree(int S)
{
	for(ri int i=0;i<=n;i++) Dis[i]=INF;
	memset(book,0,sizeof(book));
	Dis[S]=0;
	while(1)
	{
		int x=0;
		for(ri int i=1;i<=n;i++) if(!book[i] && Dis[i]<Dis[x]) x=i;
		if(!x) break;
		book[x]=1;
		for(ri int i=1;i<=n;i++) if(Dis[i]>Dis[x]+W[x][i]) Dis[i]=Dis[x]+W[x][i];
	}
}
signed main()
{
	n=read(), m=read();
	for(ri int i=0;i<=n;i++)
	for(ri int j=0;j<=n;j++) W[i][j]=INF;
	for(ri int i=1;i<=m;i++)
	{
		U[i]=read(), V[i]=read(), C[i]=read(), D[i]=read();
		if(W[U[i]][V[i]]>=C[i]) f[U[i]][V[i]]=W[U[i]][V[i]], W[U[i]][V[i]]=C[i];
		else f[U[i]][V[i]]=min(f[U[i]][V[i]],C[i]);
	}
	for(ri int i=1;i<=n;i++) Dijk(i);
	Ans=dis[1][n]+dis[n][1];
	for(ri int i=1;i<=m;i++)
	{
		int res1,res2;
		int tt=W[U[i]][V[i]];
		int fir=W[V[i]][U[i]];
		W[V[i]][U[i]]=min(W[V[i]][U[i]],C[i]);
		W[U[i]][V[i]]=f[U[i]][V[i]];
		res1=res2=INF;
		if(pre[1][V[i]]!=U[i]||dis[1][V[i]]!=dis[1][U[i]]+C[i]) res1=min(dis[1][n],dis[1][V[i]]+C[i]+dis[U[i]][n]);
		else Tree(1), res1=Dis[n];
		if(pre[n][V[i]]!=U[i]||dis[n][V[i]]!=dis[n][U[i]]+C[i]) res2=min(dis[n][1],dis[n][V[i]]+C[i]+dis[U[i]][1]);
		else Tree(n), res2=Dis[1];
		W[U[i]][V[i]]=tt, W[V[i]][U[i]]=fir;
		Ans=min(Ans,D[i]+res1+res2);
	}
	printf("%lld\n",(Ans>=1e16)?(-1):Ans);
	return 0;
}
posted @ 2021-02-25 13:53  zkdxl  阅读(115)  评论(0编辑  收藏  举报