首页 > 其他分享 >P1266 速度限制 题解

P1266 速度限制 题解

时间:2024-02-28 15:24:42浏览次数:28  
标签:ch int 题解 void 速度限制 inline P1266 speed dis

考虑分层图。

把图按速度分成 \(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;	
}

标签:ch,int,题解,void,速度限制,inline,P1266,speed,dis
From: https://www.cnblogs.com/syzqwq/p/18040514

相关文章

  • ABC302 Ex 题解
    首先我们考虑\(v\)固定怎么做。实际上就是ARC111B。考虑建图,对每个\((a_i,b_i)\)建一条无向边,那么问题就变成了:对于每条边都要选择它以及其连接的一个点,最大化选出的点数。很明显可以对每个连通块分开考虑。记当前连通块的点数为\(V\),边数为\(E\)。那么有结论:该连通块对......
  • ABC301 Ex 题解
    题意:你有一张无向连通图,定义\(d(s,t)\)表示从\(s\)到\(t\)所有路径中最大边权的最小值。现在给你三个数\(A_i,S_i,T_i\),让你求出当\(A_i\)这条边的边权\(+1\)时,\(d(S_i,T_i)\)会增加多少。首先考虑一下\(A_j+1\)什么时候会对\(d(S_j,T_j)\)有影响。\(A_j>d(S_......
  • 第五届图灵杯中级组题解
    网址赛时得分\(25+50+5+1=81\),rk67,逊死了。赛后发现T1爆搜+剪枝有\(50\)分,我【】【】。总结:还是菜。A.等差数列首先特判\(n\le4\)的情况。此时答案必然为Yes,只需要两两分组即可。由于第一个数必然在其中一个等差数列内,不妨强制令其在第一个等差数列内。考虑......
  • CF351D - Jeff and Removing Periods 题解
    首先做一点显然的转化:在进行第一次操作之后,可以将相同的数排在一起,这样一次就能删掉一种数。如果一开始就能删光一种数的话,那么次数就是区间颜色数,否则就是区间颜色数\(+1\)。所以现在原问题变成了两个问题:求区间内不同颜色数,判断区间内是否有某种颜色满足其出现位置构成等差数......
  • P9755 [CSP-S 2023] 种树 题解
    首先考虑如何求出第\(i\)棵树在\([l,r]\)时间段能长多高。这个东西可以差分一下然后等差数列求和。放一下代码:inlinelllcalc(inti,intx){ if(c[i]>=0){ return(lll)b[i]*x+(lll)c[i]*x*(x+1)/2; }else{ intd=(b[i]-1)/(-c[i]); if(x<=d)return(lll)b[i]*x+(l......
  • A-E题解
    A:对于任意一个满足条件的2*2矩阵,要么3个R和1个W,要么3个W和1个R。我们以3个R和1个W举例,只有以下4中情况满足:RR   RR   RW   WRRW  WR  RR   RR所以一种构造方法如下:奇数行全部放R;偶数行奇数列放R,偶数列放W即可。voidsolve(){intn;......
  • CF756D Bacterial Melee 题解
    给一个\(O(n^2)\)的做法。考虑从左到右扫描最终得到的字符串,如果当前的字符和前一个字符相同,就删掉这个字符。由题意可知,最终得到的字符串一定是\(s\)的一个子序列。我们定义状态\(dp[i][j]\)表示:当前子序列的最后一个字符是\(s[i]\),已经填完了最终字符串的前\(j\)个字......
  • CF1034E Little C Loves 3 III 题解
    这道题与P6097【模板】子集卷积基本相同,但是每个元素的值属于\([0,3]\),且\(n\le21\),时限\(\rm1s\)。在做P6097这道题的时候,我们多开了一维用来记录二进制下\(1\)的个数。但是这道题每个元素的值只属于\([0,3]\),我们可以用一种十分巧妙的方法:我们设\(f(x)\)表示\(......
  • P2065 [TJOI2011] 卡片 题解
    看大家建图时中间都连了质数点,发一个不用质数点的解法。我们可以先从源点向每一个蓝色卡片对应的点连一条边,再从每一个红色卡片对应的点向汇点连一条边。如果两张卡片可以一起拿走,那就在它们之间连一条边(蓝色连到红色),这些边的最大流量都是\(1\)。建好图以后我们就可以直接用Di......
  • CF111D Petya and Coloring 题解
    很明显这是一道组合题。首先特判一下,当\(m=1\)时,答案就是\(k^n\)。对于\(m>1\)的情况,我们可以得出一个结论:对于沿格子的线穿过的任何垂直线,会将棋盘分成两个非空的部分,这两个部分中的不同颜色的数量相同且总是不变。设这个不同颜色的数量为\(i\),那么左边这部分的颜色一定......