1. 程式人生 > 其它 >[JOI 2020 Final] 奧運公交 題解

[JOI 2020 Final] 奧運公交 題解

Statement

JOI 2020 Final オリンピックバス - 洛谷

Solve

5pts

暴力列舉更改邊,然後直接上 \(dijkstra\)

考慮到堆優化的複雜度是 \((n+m)\log n\) ,而不優化是 \(n^2\)

所以在本題中,我們應不加堆,那麼總複雜度為 \(O(n^2m)\)

100pts

考慮對我們剛剛的暴力進行優化

發現對複雜度影響最大的其實是 \(m\)

發現我們在每次列舉邊,修改邊,重新計算的過程中

我們可以先求出 \(SPT\) ,即最短路徑生成樹

Dijkstra演算法:每一個點的最短路都是有另外一個點更新的;

假設將i的最短路更新節點記為:pre[i]

讓整個圖只保留<pre[i], i>,那麼就是一棵樹;

這棵樹被稱為最短路徑樹(Shortest Path Tree)

對於一條邊:

  • 不在最短路徑生成樹中:\(O(1)\)

    • 就是原最短路
    • 必須經過該邊,但路徑上的其他邊都必須是在最短路徑樹上
  • 在最短路徑生成樹中:\(O(n^2)\)

    翻轉後,重新計算即可

顯然, \(SPT\) 上只有 \(O(n)\) 條邊

那麼,這樣的複雜度就是 \(O(n^3+m)\)

考慮具體實現,我們知道從 \(i\to 1\) 和從 \(1\to i\) 不是一回事

為了 \(O(1)\) 地解決邊不在 \(SPT\) 中的情況,我們需要四顆 \(SPT\) (程式碼

  • \(g[0].dis[i]\) 表示 \(1\to i\)
  • \(g[1].dis[i]\) 表示 \(i\to n\)
  • \(g[2].dis[i]\) 表示 \(i\to 1\)
  • \(g[3].dis[i]\) 表示 \(n\to i\)

Code

參考 題解 P6880 - Time_tears 的部落格

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf = 1e18;
const int N = 205;
const int M = 5e4+5;

int read(){
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch>='0'&&ch<='9')s=s*10+ch-'0',ch=getchar();
	return s*w;
}

int n,m,ans;
int x[M],y[M],c[M],d[M];

struct Graph{
	struct Edge{
		int nex,to,dis;
	}edge[M];
	int head[N],pre[N],d1[N],d2[N];
    //pre[i] 最短路徑樹父邊;d1 未翻轉;d2 翻轉某條邊 
	bool exist[M],vis[N];//exist[i] 邊 i 是否在 spt 中
	int st,sp,elen;//sp 被翻轉的邊

	void add(int u,int v,int w){
		edge[++elen]={head[u],v,w};
		head[u]=elen;
	}
	void dijkstra1(){
		for(int i=1;i<=n+1;++i)d1[i]=inf;//注意這裡到n+1
		memset(vis,0,sizeof(vis)),d1[st]=0;
		for(int i=1;i<=n;++i){
			int u=n+1;
			for(int j=1;j<=n;++j)
				if(!vis[j]&&d1[j]<d1[u])u=j;
			if(u==n+1)break;vis[u]=1;
			for(int e=head[u],v;e;e=edge[e].nex)
				if(!vis[v=edge[e].to]&&d1[v]>d1[u]+edge[e].dis)
					d1[v]=d1[u]+edge[e].dis,pre[v]=e;
		}
		for(int i=1;i<=n;++i)if(i!=st)exist[pre[i]]=1;
	}
	void dijkstra2(){
		for(int i=1;i<=n+1;++i)d2[i]=inf;
		memset(vis,0,sizeof(vis)),d2[st]=0;
		for(int i=1;i<=n;++i){
			int u=n+1;
			for(int j=1;j<=n;++j)
				if(!vis[j]&&d2[j]<d2[u])u=j;
			if(u==n+1)break;vis[u]=1;
			for(int e=head[u],v;e;e=edge[e].nex)
				if(!vis[v=edge[e].to]&&sp!=e&&d2[v]>d2[u]+edge[e].dis)
					d2[v]=d2[u]+edge[e].dis;
            //這裡將翻轉實現為不進行鬆弛,因為顯然即使鬆弛,對答案沒有影響
		}
	}
	int calc(int e,int pos){//翻轉邊 e,問到點 pos 的最短路
		if(exist[e]){//在 spt 中
			if(sp!=e)sp=e,dijkstra2();//重新算
			return d2[pos];
		}
		else return d1[pos];
	}
}g[4];

signed main(){
	n=read(),m=read();
	g[0].st=g[2].st=1,g[1].st=g[3].st=n;
	for(int i=1;i<=m;++i)
		x[i]=read(),y[i]=read(),c[i]=read(),d[i]=read(),
		g[0].add(x[i],y[i],c[i]),g[1].add(y[i],x[i],c[i]),
		g[2].add(y[i],x[i],c[i]),g[3].add(x[i],y[i],c[i]);
	for(int i=0;i<4;++i)g[i].dijkstra1();
	ans=g[0].d1[n]+g[3].d1[1];//不翻轉
	for(int i=1;i<=m;++i)
		ans=min(ans,min(g[0].calc(i,n),g[0].calc(i,y[i])+c[i]+g[1].calc(i,x[i]))+d[i]+
					min(g[3].calc(i,1),g[3].calc(i,y[i])+c[i]+g[2].calc(i,x[i])));
    	//min(ans,min(1->n,1->v->u->n)+翻轉費用+min(n->1,n->v->u->1))
	printf("%lld\n",ans<1e17?ans:-1);
	return 0;
}