1. 程式人生 > 實用技巧 >「JOI 2020 Final」奧運公交 (最短路)

「JOI 2020 Final」奧運公交 (最短路)

「JOI 2020 Final」奧運公交 (最短路)

問題實際上就是要分別求\(1-n\)\(n-1\)對於每一條邊翻轉之後的最短路

由於\(m\)的上限為\(n^2\),下面所說的\(\text{Dijkstra}\)都是沒有堆優化的板本,即\(n^2\)找最小點,\(m\)更新

以計算\(1-n\)為例

不妨先考慮計算刪除每一條邊\((u,v,c)\)之後,1為源點的的最短路情況

我們知道從源點\(S\)出發的最短路可以用最短路圖描述,而最短路圖是一張拓撲圖(fix:這道題含有0邊,所以並不是,但是沒有關係)

如果從最短路圖中提取一棵樹,那麼顯然只有這些樹邊需要考慮刪除之後對於最短路的影響

對於這些邊重新求最短路即可,複雜度為\(O(n(m+n^2))\)

如何考慮翻轉一條邊之後的貢獻?

不妨再求出以\(n\)為結束的最短路,即求反圖\(n\)為源點的答案

然後在兩個最短路上查詢一下即可得到\(1-n\)的最短路

同理得到\(n-1\)的答案

複雜度為\(O(n(m+n^2))\)

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef double db;
typedef pair <int,int> Pii;
#define reg register
#define mp make_pair
#define pb push_back
#define Mod1(x) ((x>=P)&&(x-=P))
#define Mod2(x) ((x<0)&&(x+=P))
#define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(int i=a,i##end=b;i>=i##end;--i)
template <class T> inline void cmin(T& a,T b){ ((a>b)&&(a=b)); }
template <class T> inline void cmax(T& a,T b){ ((a<b)&&(a=b)); }

char IO;
template <class T=int> T rd(){
	T s=0;int f=0;
	while(!isdigit(IO=getchar())) f|=IO=='-';
	do s=(s<<1)+(s<<3)+(IO^'0');
	while(isdigit(IO=getchar()));
	return f?-s:s;
}


bool Mbe;
const int N=210,M=5e4+10,INF=2e9+10;

int n,m;
int E[N][N],E2[N][N],EI[N][N],dis[N],vis[N];
void Dijkstra(int S){
	rep(i,0,n) dis[i]=INF,vis[i]=0;
	dis[S]=0;
	while(1) {
		int u=0;
		rep(i,1,n) if(!vis[i] && dis[i]<dis[u]) u=i;
		if(!u) break;
		vis[u]=1;
		rep(i,1,n) if(E[u][i]<INF) cmin(dis[i],dis[u]+E[u][i]);
	}
}

int U[M],V[M],C[M],D[M];
int mk[M];
void dfs(int u) {
	vis[u]=1;
	rep(i,1,n) if(!vis[i] && E[u][i]<INF && dis[i]==dis[u]+E[u][i]) mk[EI[u][i]]=1,dfs(i);
}

int Res1[M][N],Res2[M][N];
ll Ans[M];

void Solve(int S,int Res[M][N]){ 
    // 計算刪除每條邊之後,S為源點的最短路情況,放在Res[M][N]中
	rep(i,1,n) rep(j,1,n) E[i][j]=INF,E2[i][j]=INF;
	rep(i,1,m) {
		int u=U[i],v=V[i],c=C[i];
		if(E[u][v]>c) E2[u][v]=E[u][v],E[u][v]=c,EI[u][v]=i;
		else if(E2[u][v]>c) E2[u][v]=c;
	}
	Dijkstra(S);
	rep(i,1,n) vis[i]=0;
	rep(i,1,m) mk[i]=0;
	dfs(S);
	rep(i,1,m) if(!mk[i]) rep(j,1,n) Res[i][j]=dis[j];
	rep(i,1,m) if(mk[i]) {
		swap(E[U[i]][V[i]],E2[U[i]][V[i]]);
		Dijkstra(S);
		rep(j,1,n) Res[i][j]=dis[j];
		swap(E[U[i]][V[i]],E2[U[i]][V[i]]);
	}
}

void Solve(){ 
	Solve(1,Res1);
	rep(i,1,m) swap(U[i],V[i]);
    // 反圖計算
	Solve(n,Res2);
	rep(i,1,m) swap(U[i],V[i]);

	rep(i,1,m) {
		int t=min(Res1[i][n],Res2[i][1]);
		if(Res1[i][V[i]]<INF && Res2[i][U[i]]<INF) cmin(t,Res1[i][V[i]]+Res2[i][U[i]]+C[i]);
		Ans[i]+=t; // 合併貢獻
	}
}

bool Med;
int main(){ 
	//fprintf(stderr,"%.2lf\n",(&Med-&Mbe)/1024.0/1024.0);
	n=rd(),m=rd();
	rep(i,1,m) U[i]=rd(),V[i]=rd(),C[i]=rd(),D[i]=rd();
	ll ans=0;
	rep(i,1,n) rep(j,1,n) E[i][j]=INF;
	rep(i,1,m) cmin(E[U[i]][V[i]],C[i]);
	Dijkstra(1),ans+=dis[n];
	Dijkstra(n),ans+=dis[1];
	
    // 計算1-n
	Solve();
    // 計算n-1
	rep(i,1,m) U[i]=n-U[i]+1,V[i]=n-V[i]+1;
	Solve();
	rep(i,1,m) cmin(ans,Ans[i]+D[i]);
	printf("%lld\n",ans>=INF?-1:ans);
}