[JOI 2020 Final] 奧運公交 題解
阿新 • • 發佈:2021-08-23
Statement
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
#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; }