题解 [NOI2014]魔法森林
这题看起来一大堆 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);
}