【BZOJ2725】[Violet 6] 故乡的梦(最短路+线段树)
大致题意: 给定一张图,每次询问禁止走某条路后\(S\)与\(T\)间的最短路。
前言
以前的我只是菜,现在的我不仅菜,还瞎。。。
我居然以为起点和终点是每次询问给出的,结果想了半天。。。
最短路
考虑我们找出原图任意一条\(S\)到\(T\)的最短路。
显然,在禁止走不在最短路上的边时,最短路不会受到影响。
否则,假设我们断开了\((x,y)\)(设\(x\)离\(S\)较近,\(y\)离\(T\)较近),那么我们必然是找到分别在最短路中\(S\)到\(x\)、\(y\)到\(T\)的路径上的两个点\(u,v\),使得最短路中\(S\)到\(u\)的距离加上\(u\)到\(v\)的距离加上最短路中\(v\)到\(T\)的距离最小。
我们可以给\(S\)到\(T\)最短路上的点依次重新标号,其中第\(i\)个点\(x\)的\(rk_x=i\)。
然后令\(fS_x,fT_x\)分别表示\(x\)到\(S,T\)最短路上第一个位于\(S\)到\(T\)最短路上点的\(rk\)(可以拓扑求,详见代码)。
于是对于一条不在最短路上的边\((u,v)\),强制经过它的最短路就是\(disS_u+val_{u,v}+disT_v\),它能替代的最短路上边的区间就是\([fS_u,fT_v-1]\)。
这个操作就相当于是相当于区间修改,单点查询最小值。
则我们显然可以用线段树来维护。
代码
#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 200000
#define LL long long
#define INF 1e18
#define Gmin(x,y) (x>(y)&&(x=(y)))
#define min(x,y) ((x)<(y)?(x):(y))
#define add(x,y,v) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y,e[ee].val=v)
using namespace std;
int n,m,s,t,ee,cnt,lnk[N+5],lst[N+5],rk[N+5];struct line {int x,y,v;}p[N+5];
struct edge {int to,nxt,val;}e[2*N+5];
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 writeln(Ty x) {W(S[++T]=x%10+48,x/=10);W(T) pc(S[T--]);pc('\n');}
I void NA() {pc('I'),pc('n'),pc('f'),pc('i'),pc('n'),pc('i'),pc('t'),pc('y'),pc('\n');}
I void clear() {fwrite(FO,1,C-FO,stdout),C=FO;}
#undef D
}F;
class Dijkstra//最短路
{
private:
#define mp make_pair
#define fir first
#define sec second
LL dis[N+5];int vis[N+5],pre[N+5],f[N+5],d[N+5],p[N+5];
typedef pair<LL,int> Pr;priority_queue<Pr,vector<Pr>,greater<Pr> > q;
public:
I LL operator [] (CI x) {return dis[x];}
I int operator () (CI x) {return f[x];}
I void Dij(CI x)//最短路
{
RI i;Pr k;for(i=1;i<=n;++i) dis[i]=INF;q.push(mp(dis[x]=0,x));
W(!q.empty())
{
if(k=q.top(),q.pop(),vis[k.sec]) continue;vis[k.sec]=1;
for(i=lnk[k.sec];i;i=e[i].nxt) k.fir+e[i].val<dis[e[i].to]&&
(q.push(mp(dis[e[i].to]=k.fir+e[i].val,e[i].to)),pre[e[i].to]=k.sec);
}
}
I void Topo()//拓扑求出f
{
RI i,k,H=1,T=0;for(i=1;i<=n;++i) ++d[pre[i]];for(i=1;i<=n;++i) !d[i]&&(p[++T]=i);
W(H<=T) !--d[pre[k=p[H++]]]&&(p[++T]=pre[k]);
for(i=T;i;--i) f[p[i]]=rk[p[i]]?rk[p[i]]:f[pre[p[i]]];//倒序枚举数组中的点求f
}
I void GetRk(CI s,RI t)//重新标号
{
for(RI i=1;i<=n;++i) lst[i]=pre[i];
W(rk[t]=++cnt,t^s) t=lst[t];for(RI i=1;i<=n;++i) rk[i]&&(rk[i]=cnt-rk[i]+1);//枚举每个点
}
}D1,D2;
class SegmentTree//线段树,这里用了标记永久化
{
private:
#define PT CI l=1,CI r=cnt,CI rt=1
#define LT l,mid,rt<<1
#define RT mid+1,r,rt<<1|1
LL V[N<<2];
public:
I void Build(PT) {if(V[rt]=INF,l==r) return;int mid=l+r>>1;Build(LT),Build(RT);}
I void U(CI L,CI R,LL v,PT)//区间修改
{
if(!L||!R||L>R) return;if(L<=l&&r<=R) return (void)Gmin(V[rt],v);
int mid=l+r>>1;L<=mid&&(U(L,R,v,LT),0),R>mid&&(U(L,R,v,RT),0);
}
I LL Q(CI x,PT)//单点查询
{
if(l==r) return V[rt];int mid=l+r>>1;
LL t=x<=mid?Q(x,LT):Q(x,RT);return min(t,V[rt]);
}
}S;
int main()
RI Qt,i,x,y,z;for(F.read(n,m),i=1;i<=m;++i)
F.read(p[i].x,p[i].y,p[i].v),add(p[i].x,p[i].y,p[i].v),add(p[i].y,p[i].x,p[i].v);
if(F.read(s,t,Qt),s==t) {for(i=1;i<=Qt;++i) puts("0");return 0;}//特判
if(D1.Dij(s),D1[t]==INF) {for(i=1;i<=Qt;++i) puts("Infinity");return 0;}//特判
D1.GetRk(s,t),D1.Topo(),D2.Dij(t),D2.Topo(),S.Build();
#define Check(x,y) (!rk[x]||!rk[y]||(rk[x]^(rk[y]+1)&&rk[y]^(rk[x]+1)))//判断一条边是否在最短路上
for(i=1;i<=m;++i) Check(p[i].x,p[i].y)&&//枚举不在最短路上的边
(
S.U(D1(p[i].x),D2(p[i].y)-1,D1[p[i].x]+p[i].v+D2[p[i].y]),//(x,y)
S.U(D1(p[i].y),D2(p[i].x)-1,D1[p[i].y]+p[i].v+D2[p[i].x]),0//(y,x)
);
LL k;W(Qt--)//处理询问
{
if(F.read(x,y),Check(x,y)) {F.writeln(D1[t]);continue;}//不在最短路上直接输出最短路
(k=S.Q(min(rk[x],rk[y])))==INF?F.NA():F.writeln(k);//不连通输出无解,否则输出答案
}return F.clear(),0;
}
待到再迷茫时回头望,所有脚印会发出光芒