題解 [NOI2014]魔法森林
阿新 • • 發佈:2020-08-19
這題看起來一大堆 LCT 的題解,那我就來發一個動點 SPFA 的吧。
SPFA 是啥不用我說了吧。
動點SPFA
這是一個動態加邊的演算法。
我們現在假設圖中已經有了一些邊,從源點\(v0\)的最短路為\(d_1,d_2,d_3,\dots,d_n\)。
我們考慮加一條邊\((u,v,d)\),表示從\(u\)到\(v\)的一條邊,長度為\(d\)。
這時我們發現我們只要把\(u,v\)插入佇列中跑一邊 SPFA 就行了。
想一想,為什麼。
因為我們只是添加了這一條邊,別的邊的最短路都處理好了。所以如果我們要更新最短路,一定要經過這條邊。這下明白了把?
同時由於之前處理過了。所以時間複雜度也會大大降低。
不過由於總會有毒瘤出題人卡 SPFA 所以有別的演算法時最好別用
Solution
這題的題解我們首先看到最大值最小會想到二分,但是因為邊沒有雙單調性,所以這樣做不具有正確性。
那麼我們可以將所有的邊按\(a_i\)排序,列舉\(i\),動態加邊,動點 SPFA ,求出從\(1\)開始經過的\(b\)邊權最大值的最小值,這個就是三角不等式變個形,具體見程式碼。
然後當前經過的a邊權的最大值就是\(a_i\),不然的話就是沒經過這條邊,和上一次的答案時一樣的。
最後取一下最小值就可以了。
程式碼
#pragma GCC optimize(2) #include<bits/stdc++.h> #include<tr1/unordered_map> #define re register #define N 401001 #define MAX 2001 #define inf 1e18 #define eps 1e-10 using namespace std; typedef long long ll; typedef double db; inline void read(re ll &ret) { ret=0;re ll pd=0;re char c=getchar(); while(!isdigit(c)){pd|=c=='-';c=getchar();} while(isdigit(c)){ret=(ret<<1)+(ret<<3)+(c&15);c=getchar();} ret=pd?-ret:ret; return; } ll n,m,a,b,x,y,head[N],ans=inf,tot; ll d[N]; struct edge { ll from,to,x,y,nxt; inline friend bool operator <(re edge a,re edge b) { return a.x<b.x; } }e[N],f[N]; inline void add(re ll u,re ll v,re ll dx,re ll dy) { e[++tot].from=u; e[tot].to=v; e[tot].x=dx; e[tot].y=dy; e[tot].nxt=head[u]; head[u]=tot; return; } queue<ll>q; bool vis[N]; signed main() { read(n); read(m); for(re int i=1;i<=m;i++) { read(f[i].from); read(f[i].to); read(f[i].x); read(f[i].y); } sort(f+1,f+m+1); for(re int i=1;i<=n;i++) d[i]=inf; d[1]=0; for(re int i=1;i<=m;i++) { add(f[i].from,f[i].to,f[i].x,f[i].y); add(f[i].to,f[i].from,f[i].x,f[i].y); q.push(f[i].from); q.push(f[i].to); vis[f[i].from]=vis[f[i].to]=true; while(!q.empty()) { re ll ver=q.front(); q.pop(); vis[ver]=false; for(re int j=head[ver];j;j=e[j].nxt) { re ll to=e[j].to,dis=e[j].y; if(d[to]>max(d[ver],dis)) { d[to]=max(d[ver],dis); if(!vis[to]) { vis[to]=true; q.push(to); } } } } ans=min(ans,d[n]+f[i].x); } if(ans==inf) puts("-1"); else printf("%lld\n",ans); exit(0); }