把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【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;
}
posted @ 2020-06-02 09:21  TheLostWeak  阅读(148)  评论(0编辑  收藏  举报