【最短路优化建图(dij)+线段树】BZOJ4699 树上的最短路

【题目】
BZOJ
一棵 n n n个节点的无向树,边有边权。另有 m m m条通道,形如 ( a , b , c , d , v ) (a,b,c,d,v) (a,b,c,d,v),表示 ( a , b ) (a,b) (a,b)路径上所有点向 ( c , d ) (c,d) (c,d)路径上所有点都连了一条边权为 v v v的有向边。求任意节点出发到达 K K K的最短路。
n ≤ 2.5 × 1 0 5 , m ≤ 1 0 5 n\leq 2.5\times 10^5,m\leq 10^5 n2.5×105,m105,边权不超过 1 0 9 10^9 109

【解题思路】
从暴力出发,我们可以将有向边反向,这样就变成了一个单源最短路。

一种可行的优化是类似网络流建图优化,对树进行链剖,那么一条路径对应 O ( log ⁡ 2 n ) O(\log^2 n) O(log2n)个线段树区间,总共就是 O ( m log ⁡ 4 n ) O(m\log^4 n) O(mlog4n)条边,显然这个做法是 naive \text{naive} naive的。当然并不需要每个区间到区间都连边,只需要建出一个中转点,就可以建 O ( m log ⁡ 2 n ) O(m\log ^2 n) O(mlog2n)条边了,然而仍然是 naive \text{naive} naive的。

既然都链剖了,那不妨使用更简单的倍增。类似上面的方法,可以将根据原树建成至少有 O ( n log ⁡ n ) O(n\log n) O(nlogn)条边的图。如果我们将每条通道路径划分成 O ( log ⁡ n ) O(\log n) O(logn)个区间并建立中转点连边,额外连边是 O ( m log ⁡ n ) O(m\log n) O(mlogn)条的,但若是利用倍增的性质,类似 RMQ \text{RMQ} RMQ,将一段拆成有交但并为全集的两段,额外连边只有 O ( m ) O(m) O(m)条。

这样可以做到 O ( n log ⁡ 2 n ) O(n\log^2 n) O(nlog2n)级别的复杂度,按道理来说也能过吧。

不妨考虑 dijkstra \text{dijkstra} dijkstra算法,会按照 K K K到某点最短路顺序访问点,每个点只会被访问一次,因此若通道 ( a , b ) (a,b) (a,b)中任意一点在当前使用了通道,且接下来的一步不存在使用通道的更新时,这个通道将不会再被使用。

考虑如何得到经过 x x x的所有未使用过的通道,容易发现满足前提经过 x x x的通道有两种情况:

  • 一端在 x x x子树内,另一端不在
  • 两端的 l c a lca lca x x x

其中第二种情况比较好处理,我们只需要开一个 vector \text{vector} vector,存下以每个点为 l c a lca lca的所有通道即可。

对于第一种情况,我们先求出 dfs \text{dfs} dfs序,若通道是 ( a , b ) → ( c , d ) (a,b)\rightarrow (c,d) (a,b)(c,d),且 d f n a < d f n b dfn_a<dfn_b dfna<dfnb,那么第一种情况是 a a a x x x子树内而 b b b不在,第二种情况则相反。下面对第一种情况进行解释,第二种情况类似。

第一种情况满足的是 d f n x ≤ d f n a ≤ d f n x + s i z x − 1 , d f n b > d f n x + s i z x − 1 dfn_x\leq dfn_a\leq dfn_x+siz_x-1,dfn_b>dfn_x+siz_x-1 dfnxdfnadfnx+sizx1,dfnb>dfnx+sizx1
那么我们只需要不断询问 d f n a dfn_a dfna在一段区间内可行 d f n b dfn_b dfnb最大的通道,这个用线段树按 dfs \text{dfs} dfs序维护终点 d f n dfn dfn最大的通道不断取出,直到 d f n b dfn_b dfnb也在这段区间内即可。具体实现上,线段树维护区间中一个端点的 d f n dfn dfn在这个区间内的通道,另一个端点的 d f n dfn dfn的最大值,每个叶子用堆维护以这个点为端点的所有通道的另一个端点可以直接对以这个点为端点的所有通道的另一个端点排序。

由于树边实际上也可以看作是通道,剩下的问题就是最短路了,我们可以维护一个小根堆来取出最优的通道 ( a , b , c , d , v ) (a,b,c,d,v) (a,b,c,d,v),每次暴力将 ( c , d ) (c,d) (c,d)路径上所有没有访问过的点的出边都取出。具体实现上,我们用 DSU \text{DSU} DSU,记 f a i fa_i fai为离 i i i最近的未被访问过的祖先,每次访问 i i i后,令 f a i = f i n d f ( f a i ) fa_i=findf(fa_i) fai=findf(fai)即可(就是路径压缩咯)。

总时间复杂度是 O ( n log ⁡ n ) O(n\log n) O(nlogn)级别的。

#include
#define pb push_back
#define mkp make_pair
#define fi first
#define se second
using namespace std;

typedef long long ll;
typedef pair<int,int> pii;
const int N=3e5+10;
int n,m,S,fc[22];

namespace IO
{
	int read()
	{
		int ret=0;char c=getchar();
		while(!isdigit(c)) c=getchar();
		while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
		return ret;
	}
	void write(ll x){if(x>9)write(x/10);putchar(x%10^48);}
	void writeln(ll x){write(x);putchar('\n');}
}
using namespace IO;

namespace Tree
{
	int ind,tot,head[N],st[N],ed[N],dep[N],fa[20][N];
	struct Tway{int v,w,nex;}e[N<<1];
	void add(int u,int v,int w)
	{
		e[++tot]=(Tway){v,w,head[u]};head[u]=tot;
		e[++tot]=(Tway){u,w,head[v]};head[v]=tot;
	}
	void dfs(int x)
	{
		st[x]=++ind;
		for(int i=1;fc[i]<dep[x];++i) fa[i][x]=fa[i-1][fa[i-1][x]];
		for(int i=head[x];i;i=e[i].nex)
		{
			int v=e[i].v;
			if(v==fa[0][x]) continue;
			fa[0][v]=x;dep[v]=dep[x]+1;dfs(v);
		}
		ed[x]=ind;
	}
	int lca(int x,int y)
	{
		if(dep[x]<dep[y]) swap(x,y);
		for(int i=0,t=dep[x]-dep[y];i<20;++i)
			if(t&fc[i]) x=fa[i][x];
		for(int i=19;~i;--i) if(fa[i][x]^fa[i][y])
			x=fa[i][x],y=fa[i][y];
		return x==y?x:fa[0][x];
	}
}
using namespace Tree;

namespace Segment
{
#define ls (x<<1)
#define rs (x<<1|1)
	int nl[N],nr[N];
	vector<int>vec[N];
	vector<pii>lef[N];
	struct Segment_Tree
	{
		pii mi[N<<2],mx[N<<2];
		void pushup(int x){mi[x]=min(mi[ls],mi[rs]);mx[x]=max(mx[ls],mx[rs]);}
		void build(int x,int l,int r)
		{
			if(l==r)
			{
				if(lef[l].size()) mi[x]=lef[l][nl[l]],mx[x]=lef[l][nr[l]];
				else mi[x]=mkp(N,0),mx[x]=mkp(0,0);
				//cerr<
				return;
			}
			int mid=(l+r)>>1;
			build(ls,l,mid);build(rs,mid+1,r);
			pushup(x);
		}
		void update(int x,int l,int r,int p,int op)//op==0 delete min,op==1 delete max
		{
			if(l==r)
			{
				if(op) --nr[p]; else ++nl[p];
				if(nl[p]<=nr[p]) mi[x]=lef[p][nl[p]],mx[x]=lef[p][nr[p]];
				else mi[x]=mkp(N,0),mx[x]=mkp(0,0);
				return;
			}
			int mid=(l+r)>>1;
			if(p<=mid) update(ls,l,mid,p,op);
			else update(rs,mid+1,r,p,op);
			pushup(x);
		}
		pii query(int x,int l,int r,int L,int R,int op)//op==0 query min,op==1,query max
		{
			if(L<=l && r<=R) return op?mx[x]:mi[x];
			int mid=(l+r)>>1;pii res=op?mkp(0,0):mkp(N,0);
			if(L<=mid) res=op?max(res,query(ls,l,mid,L,R,op)):min(res,query(ls,l,mid,L,R,op));
			if(R>mid) res=op?max(res,query(rs,mid+1,r,L,R,op)):min(res,query(rs,mid+1,r,L,R,op));
			return res;
		}
	}tr;
#undef ls
#undef rs
}
using namespace Segment;

namespace Dijkstra
{
	ll dis[N];int f[N];
	struct data
	{
		int a,b,c,d,w;
		data(int _a=0,int _b=0,int _c=0,int _d=0,int _w=0):a(_a),b(_b),c(_c),d(_d),w(_w){}
	}a[N<<1];
	struct ele
	{
		ll w;int x,y;
		ele(ll _w=0,int _x=0,int _y=0):w(_w),x(_x),y(_y){}
		bool operator <(const ele&rhs)const{return w>rhs.w;}
	};
	priority_queue<ele>q;
	int findf(int x){return f[x]==x?x:f[x]=findf(f[x]);}
	void insert(int x,ll w)
	{
	//puts("insert");
	//printf("range:%d %d\n",st[x],ed[x]);
		for(int i=head[x];i;i=e[i].nex) q.push(ele(w+e[i].w,e[i].v,e[i].v));
		for(int i=0;i<(int)vec[x].size();++i) q.push(ele(a[vec[x][i]].w+w,a[vec[x][i]].c,a[vec[x][i]].d));
	//puts("mid");
		for(int i=tr.query(1,1,n,st[x],ed[x],0).se;i && st[a[i].a]<st[x];i=tr.query(1,1,n,st[x],ed[x],0).se)
			tr.update(1,1,n,st[a[i].b],0),q.push(ele(a[i].w+w,a[i].c,a[i].d));
		for(int i=tr.query(1,1,n,st[x],ed[x],1).se;i && st[a[i].b]>ed[x];i=tr.query(1,1,n,st[x],ed[x],1).se)
			tr.update(1,1,n,st[a[i].a],1),q.push(ele(a[i].w+w,a[i].c,a[i].d));
		dis[x]=w;
	//puts("endinsert");
	}
	void solve()
	{
		for(int i=1;i<=n;++i) f[i]=i;
		dis[S]=0;insert(S,0);f[S]=fa[0][S];
		while(!q.empty())
		{
			ele t=q.top();int x=t.x,y=t.y,z=lca(x,y);ll w=t.w;q.pop();
			//printf("%d %d %d %lld\n",x,y,z,w);
			for(int i=findf(x);dep[i]>=dep[z];f[i]=f[fa[0][i]],i=findf(i)) insert(i,w);
			for(int i=findf(y);dep[i]>=dep[z];f[i]=f[fa[0][i]],i=findf(i)) insert(i,w);
		}
		for(int i=1;i<=n;++i) writeln(dis[i]);
	}
}
using namespace Dijkstra;

namespace DreamLolita
{
	void input()
	{
		n=read();m=read();S=read();
		for(int i=1,u,v,w;i<n;++i) u=read(),v=read(),w=read(),add(u,v,w);
		for(int i=1;i<=m;++i)
		{
			int x=read(),y=read(),c=read(),d=read(),w=read();
			a[i]=data(c,d,x,y,w);
		}
	}
	void init()
	{
		fc[0]=1;for(int i=1;i<=20;++i)fc[i]=fc[i-1]<<1;
		dep[1]=1;dfs(1);
		for(int i=1;i<=m;++i) 
		{
			int &u=a[i].a,&v=a[i].b; if(st[u]>st[v]) swap(u,v);
			//printf("%d %d %d\n",u,v,lca(u,v));
			vec[lca(u,v)].pb(i);lef[st[u]].pb(mkp(st[v],i));lef[st[v]].pb(mkp(st[u],i));
		}
		for(int i=1;i<=n;++i) sort(lef[i].begin(),lef[i].end()),nl[i]=0,nr[i]=lef[i].size()-1;
		tr.build(1,1,n);
	}
	void solution(){input();init();Dijkstra::solve();}
}

int main()
{
#ifndef ONLINE_JUDGE
	freopen("BZOJ4699.in","r",stdin);
	freopen("BZOJ4699.out","w",stdout);
#endif
	DreamLolita::solution();
	return 0;
}

你可能感兴趣的:(图论-最短路,数据结构-线段树)