【BZOJ4239】巴士走读(最短路)
大致题意: 有\(n\)个点,且有\(m\)辆巴士,每辆巴士在第\(X_i\)时刻从\(A_i\)出发,在第\(Y_i\)时刻到达\(B_i\)。多次询问在规定到达\(n\)号点的最晚时间时,从\(1\)号点出发的最晚时间。
前言
自己瞎想的做法,感觉非常暴力,但复杂度应该没问题。
关键是很好写,也没怎么调试就过了(只是一开始智障写\(dijkstra\)习惯性开了小根堆。。。)。
考虑一个暴力
假如我们按\(Y_i\)从小到大的顺序枚举每一条连向\(n\)号点的边,然后从这条边出发在反向图上跑最短路。
对于每个点记下到达它的最晚时间\(dis_x\),一条反向边能走需要满足\(dis_{B_i}\ge Y_i\),满足条件即可贪心地用\(X_i\)去更新\(dis_{A_i}\)。
最终\(dis_1\)就可以对所有最晚时间大于等于枚举边\(Y_i\)的询问产生贡献。
虽说这个做法显然会\(T\)掉,但其正确性也是显然的,因此只要考虑如何优化即可。
优化
我们可以发现,显然枚举边的\(Y_i\)越小,对答案的贡献范围越大。
因此,如果一条边已经被之前的枚举边访问过,就无需再访问了。
为什么可以这么做呢?
因为这题最短路很特殊,无论你的\(dis\)是多少,只要访问了这条边,都会一样地变成\(X_i\),也就是说你用\(Y_i\)更大的枚举边去访问这条边,只会得到一样的结果,而贡献范围显然不如\(Y_i\)较小的枚举边。
于是,尽管我们要跑很多次\(Dijkstra\),但每条边只会经过一次,复杂度也就得到了保证。
代码
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define M 300000
#define pb push_back
#define mp make_pair
#define fir first
#define sec second
using namespace std;
int n,m;struct edge {int to,X,Y;};vector<edge> E[N+5];
typedef pair<int,int> Pr;vector<Pr> ans;
class FastIO
{
private:
#define FS 100000
#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
#define pc(c) (C==E&&(clear(),0),*C++=c)
#define D isdigit(c=tc())
int T;char c,*A,*B,*C,*E,FI[FS],FO[FS],S[FS];
public:
I FastIO() {A=B=FI,C=FO,E=FO+FS;}
Tp I void read(Ty& x) {x=0;W(!D);W(x=(x<<3)+(x<<1)+(c&15),D);}
Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
Tp I void write(Ty x) {x<0&&(pc('-'),x=-x);W(S[++T]=x%10+48,x/=10);W(T) pc(S[T--]);}
Tp I void writeln(Con Ty& x) {write(x),pc('\n');}
I void clear() {fwrite(FO,1,C-FO,stdout),C=FO;}
#undef D
}F;
class Dijkstra
{
private:
int dis[N+5],vis[N+5],cur[N+5];priority_queue<Pr> q;
public:
I void Init() {memset(dis,-1,sizeof(dis)),ans.pb(mp(0,-1));}//初始化为-1,并在答案中加入-1表示无解
I void Dij(Con edge& e)
{
static int ti=0;vis[n]=++ti;RI i,sz;Pr k;if(dis[e.to]>=e.X) return;
q.push(mp(dis[e.to]=e.X,e.to));W(!q.empty())
{
if(k=q.top(),q.pop(),vis[k.sec]==ti) continue;vis[k.sec]=ti;
#define Eg E[k.sec][i]
for(sz=E[k.sec].size(),i=cur[k.sec];i^sz&&k.fir>=Eg.Y;++i)//cur记录当前枚举到的边
cur[k.sec]=i,dis[Eg.to]<Eg.X&&(q.push(mp(dis[Eg.to]=Eg.X,Eg.to)),0);//Dij转移
}ans.pb(mp(e.Y,dis[1]));//将当前距离存入答案数组
}
}D;
I bool cmp(Con edge& x,Con edge& y) {return x.Y<y.Y;}
int main()
{
RI i,a,b,c,d;for(F.read(n,m),i=1;i<=m;++i) F.read(a,b,c,d),E[b].pb((edge){a,c,d});//存反向边
for(i=1;i<=n;++i) sort(E[i].begin(),E[i].end(),cmp);//按Y[i]排序
RI sz=E[n].size();for(D.Init(),i=0;i^sz;++i) D.Dij(E[n][i]);//枚举n号点的边跑Dijkstra
RI Qt;F.read(Qt);W(Qt--) F.read(a),//读入询问
F.writeln((--upper_bound(ans.begin(),ans.end(),mp(a,(int)1e9)))->sec);//在ans的vector中查找答案
return F.clear(),0;
}
待到再迷茫时回头望,所有脚印会发出光芒