描述php
在Byte山的山腳下有一個洞穴入口. 這個洞穴由複雜的洞室通過隧道鏈接構成. 洞穴的入口是一條筆直通向「前面洞口」的道路. 隧道互相都不交叉(他們只在洞室相遇). 兩個洞室要麼就經過隧道鏈接起來,要麼就通過若干隧道間接的相連. 如今決定組織辦一個'King's of Byteotia Cup' 比賽. 參賽者的目標就是任意選擇一條路徑進入洞穴並儘快出來便可. 一條路徑必須通過除了「前面洞口」以外還至少要通過其餘一個洞室.一條路徑中一個洞不能重複通過(除了「前面洞室」之外),相似的一條隧道也不能重複通過. 一個著名的洞穴探險家 Byteala 正準備參加這個比賽. Byteala 已經訓練了數月並且他已得到了洞穴系統的一套詳細資料. 對於每條隧道他都詳細計算了從兩個方向通過所須要的時間. 通過一個洞室的時間很短能夠忽略不記. 如今Byteala 向計算一條符合條件的最優路徑.c++
輸入算法
第一行有兩個數n 和 m (3 <= n <= 5000, 3 <= m <= 10000) 分別表示洞室的數目以及鏈接他們的隧道的數目. 洞室從1 到 n編號. 「前面洞室」的編號爲1. 接下來m 行描述了全部的隧道. 每行四個整數a,b,c,d 表示從洞室a到洞室b須要c分鐘的時間,而從洞室b到洞室a須要d分鐘的時間, 1 <= a,b <= n, a <> b, 1 <= c,d <= 10000. 你能夠假設符合要求的路徑確定存在.數組
輸出flex
輸出一行,最少須要多少時間完成比賽.優化
輸入樣例 1spa
3 3 1 2 4 3 2 3 4 2 1 3 1 1
輸出樣例 1blog
6
來源get
[POI2004]input
給你們提供一個測評地點吧:https://www.luogu.org/problemnew/show/T79047,你們也能夠加入個人團隊。數據均隨機生成,生成數據的代碼爲:
#include <cstdio> #include <ctime> #include <algorithm> using namespace std; const int MAXN=10000; int n,m,k; int main() { srand((unsigned)time(NULL)); n=rand()%4997+13;m=rand()%9997+3; printf("%d %d\n",n,m); k=rand()%(m/2)+2; for (int i=1;i<=k;i++) { if (rand()%2==1) printf("1 %d %d %d\n",rand()%n+1,rand()%MAXN+1,rand()%MAXN+1); else printf("%d 1 %d %d\n",rand()%n+1,rand()%MAXN+1,rand()%MAXN+1); } int u,v; for (int i=k+1;i<=m;i++) { u=rand()%n+1;v=rand()%n+1; while (v==n)v=rand()%n+1; printf("%d %d %d %d\n",u,v,rand()%MAXN+1,rand()%MAXN+1); } return 0; }
這個題目的意思是,從洞口$1$進入,通過其餘至少$1$個洞口後,再從洞口$1$出來,每一個洞口只能通過一次(洞口$1$除外)。求最短路徑。
這道題目因爲起點和終點同樣,咱們通常的$SPFA$、$dijkstra$等等都無論用了,更可惡的是,因爲每條邊方向不一樣,權值也不一樣,這使得$Floyd$也很差用了。那麼,咱們該怎麼辦呢?
咱們要把它轉化爲求普通的最短路!
咱們把與頂點$1$相連的點記錄下來,分別求最短路,而後再加上到點1的距離就好了。因而,你會驚奇的發現$Time~Limit~Exceeded$。那怎麼辦呢??
咱們能夠把與$1$相連的點分爲兩組,一下求這麼多的最短路。對與分組,二進制枚舉就能夠了。
AC代碼:
#include <queue> #include <cstdio> #include <bitset> #include <cstring> using namespace std; int read() { int x=0,f=1;char c=getchar(); while (c<'0' || c>'9'){if (c=='-')f=-1;c=getchar();} while (c>='0'&&c<='9'){x=(x<<1)+(x<<3)+c-48;c=getchar();} return x*f; } const int MAXN=5005; const int MAXM=20005; int n,m,c,id,now,fir; struct Qu { int dot,dis; bool operator < (const Qu tmp) const { return dis>tmp.dis; } }; struct edge { int v,w,nx; }set[MAXM],key[MAXN]; int head[MAXN],dis[MAXN]; bitset<MAXN> vis; priority_queue<Qu> Q; inline void Addedge(int u,int v,int w) { id++;set[id].v=v;set[id].w=w;set[id].nx=head[u]; head[u]=id; } inline void insert_edge(int v,int w) { now++;key[now].v=v;key[now].w=w;key[now].nx=fir; fir=now; } inline void init() { int u,v,a,b; n=read();m=read(); for (int i=1;i<=m;i++) { u=read();v=read();a=read();b=read(); Addedge(u,v,a);Addedge(v,u,b); if (u==1)insert_edge(v,b); if (v==1)insert_edge(u,a); } int x=n; while (x>0) {c++;x/=2;} } inline void dijkstra() { vis.reset();vis.set(1); for (int k=head[1];k>0;k=set[k].nx) Q.push((Qu){set[k].v,dis[set[k].v]}); int u,v; while (!Q.empty()) { u=Q.top().dot;Q.pop(); vis.set(u); for (int k=head[u];k>0;k=set[k].nx) { v=set[k].v; if (dis[u]+set[k].w<dis[v]) { dis[v]=dis[u]+set[k].w; if (!vis[v])Q.push((Qu){v,dis[v]}); } } } } int main() { init(); int ans=0x3f3f3f3f; for (int i=(1<<c);i>0;i>>=1) { memset(dis,0x3f,sizeof(dis)); for (int k=head[1];k>0;k=set[k].nx) if (set[k].v&i)dis[set[k].v]=set[k].w; dijkstra(); for (int k=fir;k>0;k=key[k].nx) if (~key[k].v&i)ans=min(ans,dis[key[k].v]+key[k].w); memset(dis,0x3f,sizeof(dis)); for (int k=head[1];k>0;k=set[k].nx) if (~set[k].v&i)dis[set[k].v]=set[k].w; dijkstra(); for (int k=fir;k>0;k=key[k].nx) if (key[k].v&i)ans=min(ans,dis[key[k].v]+key[k].w); } printf("%d\n",ans); return 0; }
而後,咱們須要證實這個方案的正確性。
首先,咱們很容易想到初版無腦枚舉,就是枚舉起點和終點,這樣的話,枚舉的複雜度是$(n*n)$,再加上$dijkstra$的時間複雜度$O(n*n)$,總的時間複雜度就是$O(n^4)$。但這種方法能夠$TLE$飛。因而,便要進行一點優化:先枚舉起點,求一遍最短路並經過$dis_i$數組記錄從起點到點$i$的距離,時間複雜度即是$O(n^3)$,效率是有一點提升,但本題的數據實在是太坑了,這個算法也被卡掉了。
因此還要找更加優化的算法。咱們能夠把數字的二進制列出來:
十進制數 | 二進制數 |
$1$ | $1$ |
$2$ | $10$ |
$3$ | $11$ |
$4$ | $100$ |
$5$ | $101$ |
$6$ | $110$ |
$7$ | $111$ |
$8$ | $1000$ |
$.~.~.$ | $.~.~.$ |
能夠發現,每一個數至少有一個位上的數的差異。咱們能夠 枚舉$1$的位置,這樣一來,咱們能夠把此位爲$1$的點分到$A$組,爲零的點分到$B$組,從$A$組出發求最短路,距離就是點$1$到他們的距離。因而枚舉的複雜度便變爲了$(log^2~n)$,因而總的時間複雜度爲$O(log^2~n*n^2)$。