P1266 速度限制 题解

考虑分层图。

把图按速度分成 \(V\) 层,\(f_{i,j}\) 表示点 \(i\) 在第 \(j\) 层(速度为 \(j\))的编号。注意:这里的速度为 \(j\) 是指到达 \(i\) 那一条边的速度(\(i\) 为终点)。

考虑一种 naive 的建边方式:

首先,记当前点为 \(u\),速度为 \(i\)\(u\) 的出边速度为 \(j\),长度为 \(l\),终点为 \(v\)

  • \(j\not=0\):遍历 \(u\) 的每条入边、出边,从 \(f_{u,i}\to f_{v,j}\) 连一条边权为 \(\frac{l}{j}\) 的边。

  • \(j=0\):对于每一层 \(k\),都从 \(f_{u,k}\to f_{v,k}\) 连一条长度为 \(\frac{l}{k}\) 的边。

\(j=0\) 时没什么优化空间,但是 \(j\not=0\) 时,建出来边的数量是 \(\text{indeg}(u)\times \text{oudeg}(u)\) 级别的,非常的寄。

那么,有没有什么优化方法呢?

注意到,对于 \(j\not=0\),每个 \(f_{u,i}\)\(f_{v,j}\) 连的边权是一样的。因此我们考虑对每个点建立一个“超级点”用于中转,记为 \(super_i\)。然后,对于每个 \(f_{i,j}\),都向 \(super_i\) 连一条权值为 \(0\) 的边。这样,对于 \(j\not=0\) 时,直接从 \(super_u\to f_{v,j}\) 连权值为 \(\frac{l}{j}\) 的边即可。

注意到一开始的速度为 \(70\),所以从 \(f_{0,70}\) 出发跑最短路。由于每个 \(f_{i,j}\) 都向 \(super_i\) 连边,所以 \(dis_{super_i}=\min_{j=1}^{V}dis_{f_{i,j}}\)。那么直接输出 \(dis_{super_d}\) 即可。

最后注意要输出路径,所以要记录每个点实际上应该是那个点,然后去重。

时间复杂度 \(O(V(n+m)\log V(n+m))\)

#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/tree_policy.hpp>
#include<ext/pb_ds/hash_policy.hpp>
#define gt getchar
#define pt putchar
typedef long long ll;
const int N=155;
const int M=1e7+5;
const int V=505;
using namespace std;
using namespace __gnu_pbds;
inline bool __(char ch){return ch>=48&&ch<=57;}
template<class T> inline void read(T &x){
	x=0;bool sgn=0;char ch=gt();
	while(!__(ch)&&ch!=EOF) sgn|=(ch=='-'),ch=gt();
	while(__(ch)) x=(x<<1)+(x<<3)+(ch&15),ch=gt();
	if(sgn) x=-x;
}
template<class T,class ...T1> inline void read(T &x, T1 &...x1){
	read(x);
	read(x1...);
}
template<class T> inline void print(T x){
	static char st[70];short top=0;
	if(x<0) pt('-');
 	do{st[++top]=x>=0?(x%10+48):(-(x%10)+48),x/=10;}while(x);
    while(top) pt(st[top--]);
}
template<class T> inline void printsp(T x){
	print(x);
	putchar(' ');
}
template<class T> inline void println(T x){
	print(x);
	putchar('\n');
}
inline void put_str(string s){
	int siz=s.size();
	for(int i=0;i<siz;++i) pt(s[i]);
	printf("\n");
}
struct edge{
	int to,nxt;
	double w;
}e[M];
int head[M],cnt,n,m,d,f[N][V],tot,super[N],pre[M],id[M];
// f[i][j]:点 i 在层 j(速度为 j)的编号 
// super[i]:点 i 的超级源点 
// id[i]:建出来的点 i 实际上是哪个点 
inline void add_edge(int f,int t,double w){
	e[++cnt].to=t;
	e[cnt].w=w;
	e[cnt].nxt=head[f];
	head[f]=cnt;
}
struct Pair{
    double val;
	int pos;
    Pair(double _val=0,int _pos=0):val(_val),pos(_pos){}
    inline bool operator>(const Pair &b)const{return val>b.val;}
};
double dis[M];
bool vis[M];
priority_queue<Pair,vector<Pair>,greater<Pair> >pq;
inline void dijkstra(int s){
    for(int i=1;i<=tot;++i) dis[i]=1e9;
    dis[s]=0;
    pq.push(Pair(0,s));
    while(pq.size()){
        int u=pq.top().pos;
        pq.pop();
        if(vis[u]) continue;
        vis[u]=1;
        for(int i=head[u];i;i=e[i].nxt){
            int v=e[i].to;
            if(dis[v]>dis[u]+e[i].w){
                dis[v]=dis[u]+e[i].w;
                pre[v]=u;
                pq.push(Pair(dis[v],v));
			}
        }
    }
}
int stk[M],top;
inline void print_path(int t){
	while(pre[t]){
		stk[++top]=t;
		t=pre[t];
	}
	stk[top+1]=-1;
	printsp(0);
	for(int i=top;i;--i) if(id[stk[i]]!=id[stk[i+1]]) printsp(id[stk[i]]);
}
signed main(){
	read(n,m,d);
	for(int i=0;i<n;++i){
		for(int j=1;j<=500;++j){
			f[i][j]=++tot;
			id[tot]=i;
		}
	}
	for(int i=0;i<n;++i) super[i]=++tot,id[tot]=i;
	for(int i=0;i<n;++i){
		for(int j=1;j<=500;++j){
			add_edge(f[i][j],super[i],0);
		}
	}
	for(int i=1;i<=m;++i){
		int u,v,speed,len;
		read(u,v,speed,len);
		if(speed){
			add_edge(super[u],f[v][speed],1.0*len/speed);
		}else{
			for(int j=1;j<=500;++j){
				add_edge(f[u][j],f[v][j],1.0*len/j);
			}
		}
	}
	dijkstra(f[0][70]);
	print_path(super[d]);
	return 0;	
}

然而,真的要把 \(500\) 层建出来吗?

我们照常建图。设 \(dis_{i,j}\) 表示到点 \(i\) 时速度为 \(j\) 时的最短路。然后分讨(延用上文的记号):

  • \(j\not=0\):用 \(dis_{u,i}+\frac{l}{j}\) 更新 \(dis_{v,j}\)

  • \(j=0\):用 \(dis_{u,i}+\frac{l}{i}\) 更新 \(dis_{v,i}\)

然后就做完了?

然而,该做法和原本的 naive 做法是等效的,转移数还是 \(\deg\times \deg\) 的。

考虑改进算法:设 \(g_i\) 表示 \(\min_{j=1}^{V}dis_{i,j}\),起到和原来的 \(super_i\) 一样的效果。然后建两个图,一个存 \(0\) 边,一个存非 \(0\) 边。然后修改 Dijkstra 的过程:

首先修改优先队列,把里面的元素修改为 \((\text{点},\text{dis 值},\text{速度})\),然后按 \(dis\) 排序。

一开始,我们往堆中加入两个元素:\((0,0,70)\)\((0,0,-1)\),为什么会有 \(-1\) 后面会讲。

现在假设取出了堆顶元素 \((u,dis_{u,i},i)\),然后按以下流程执行代码:

\(1.\) 如果 \(i\not=-1\)\(dis_{u,i}<g_u\),更新 \(g_u=dis_{u,i}\),并记录 \(g_u\) 是由速度 \(i\) 得来的(因为要输出路径)。然后往堆里加入一个 \((u,g_u,-1)\),跳至 \(2\)

\(2.\) 如果 \(i\not=-1\),遍历所有 \(0\) 边,用 \(dis_{u,i}+\frac{l}{i}\) 更新 \(dis_{v,i}\),然后满足条件入堆,并记录前驱。

\(3.\) 如果 \(i=-1\),此时的 \(dis\) 显然就是 \(g\)。只有在这种时候才去更新非 \(0\) 边,用 \(dis_{u,i}+\frac{l}{j}\) 更新 \(dis_{v,j}\),然后入堆并记录前驱。

显然,一开始 \(g_0\) 就确定了,明显 \(=0\)。所以我们需要加入点 \((0,0,-1)\)

最后答案就是 \(g_d\)

时间复杂度大约是 \(O((n+m)\log m)\)。DarkBZOJ 上快了 \(400ms\) 左右,Luogu 快了 \(250ms\)左右。

输出路径用 Dijkstra 时记录的前驱即可,具体见代码:

#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/tree_policy.hpp>
#include<ext/pb_ds/hash_policy.hpp>
#define gt getchar
#define pt putchar
typedef long long ll;
const int N=155;
const int M=1e7+5;
const int V=505;
using namespace std;
using namespace __gnu_pbds;
inline bool __(char ch){return ch>=48&&ch<=57;}
template<class T> inline void read(T &x){
	x=0;bool sgn=0;char ch=gt();
	while(!__(ch)&&ch!=EOF) sgn|=(ch=='-'),ch=gt();
	while(__(ch)) x=(x<<1)+(x<<3)+(ch&15),ch=gt();
	if(sgn) x=-x;
}
template<class T,class ...T1> inline void read(T &x, T1 &...x1){
	read(x);
	read(x1...);
}
template<class T> inline void print(T x){
	static char st[70];short top=0;
	if(x<0) pt('-');
 	do{st[++top]=x>=0?(x%10+48):(-(x%10)+48),x/=10;}while(x);
    while(top) pt(st[top--]);
}
template<class T> inline void printsp(T x){
	print(x);
	putchar(' ');
}
template<class T> inline void println(T x){
	print(x);
	putchar('\n');
}
inline void put_str(string s){
	int siz=s.size();
	for(int i=0;i<siz;++i) pt(s[i]);
	printf("\n");
}
struct Graph{
	int head[N],cnt;
	struct edge{
		int to,nxt;
		int w,speed;
	}e[M];	
	inline void add_edge(int f,int t,int w,int speed){
		e[++cnt].to=t;
		e[cnt].w=w;
		e[cnt].speed=speed;
		e[cnt].nxt=head[f];
		head[f]=cnt;
	}
}G1,G2;
// G1:speed not = 0
// G2:speed = 0
int n,m,d;
struct Pair{
	int pos,speed;
	double val;
    Pair(int _pos=0,double _val=0.0,int _speed=0):pos(_pos),val(_val),speed(_speed){}
    inline bool operator>(const Pair &b)const{return val>b.val;}
};
int pos[N];
pair<int,int>pre[N][V];
double dis[N][V],g[N];
bool vis[N][V];
priority_queue<Pair,vector<Pair>,greater<Pair> >pq;
inline void dijkstra(int s){
    for(int i=0;i<n;++i){
    	g[i]=1e9;
    	for(int j=1;j<=500;++j)	dis[i][j]=1e9;
	} 
    dis[s][70]=0,g[s]=0,pos[s]=70;
    pq.push(Pair(s,0,70));
    pq.push(Pair(s,0,-1));
    while(pq.size()){
        int u=pq.top().pos,speed=pq.top().speed;
        pq.pop();
        if(speed==-1){
        	for(int i=G1.head[u];i;i=G1.e[i].nxt){
        		int v=G1.e[i].to,sp=G1.e[i].speed;
        		double w=1.0*G1.e[i].w/sp;
        		if(dis[v][sp]>g[u]+w){
        			dis[v][sp]=g[u]+w;
        			pre[v][sp]=make_pair(u,speed);
        			pq.push(Pair(v,dis[v][sp],sp));
				}
			}
		}else{
			if(vis[u][speed]) continue;
        	vis[u][speed]=1;
        	if(dis[u][speed]<g[u]){
        		g[u]=dis[u][speed];
        		pos[u]=speed;
        		pq.push(Pair(u,g[u],-1));
			}
        	for(int i=G2.head[u];i;i=G2.e[i].nxt){
        	    int v=G2.e[i].to;
        	    double w=1.0*G2.e[i].w/speed;
        	    if(dis[v][speed]>dis[u][speed]+w){
        	        dis[v][speed]=dis[u][speed]+w;
        	        pre[v][speed]=make_pair(u,speed);
        	        pq.push(Pair(v,dis[v][speed],speed));
				}
        	}	
		}
    }
}
int stk[N],top;
inline void print_path(int t){
	int speed=pos[t];
	while(t){
		stk[++top]=t;
		int fst=pre[t][speed].first;
		int scd=pre[t][speed].second;
		if(scd==-1) scd=pos[fst];
		t=fst,speed=scd;
	}
	printsp(0);
	for(int i=top;i;--i) printsp(stk[i]);
}
signed main(){
	read(n,m,d);
	for(int i=1;i<=m;++i){
		int u,v,speed,len;
		read(u,v,speed,len);
		if(speed) G1.add_edge(u,v,len,speed);
		else G2.add_edge(u,v,len,0);	
	}
	dijkstra(0);
	print_path(d);
	return 0;	
}
posted @ 2024-02-28 15:15  Southern_Dynasty  阅读(18)  评论(0编辑  收藏  举报