【蓝桥杯 第十一届 决赛题目】出租车(SPFA+大模拟)

题目

题目链接

题目简介

一个需要等红绿灯的最短路设计问题

法一: dfs暴力(20分)

时间复杂度 O(2^n)
代码即备注:

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
//数据范围:n,m<=100; q<=30; h,w<=1e5; g,r<=1000; 
inline int read(){
	char ch=getchar();int sum=0;
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9')
		sum*=10,sum+=ch-'0',ch=getchar();
	return sum;
} 
#include<cmath>
//当前位置信息 
int up=0,rright=1,down=2,lleft=3;
int h[105],w[105];int n,m;
struct s1{
	int x1,y1,x2,y2;
	int dierction;double dis;
	double mid_x,mid_y;
	void dir(){//判断pos.dierction 
		if(x1==x2){
			if(y1>y2) dierction=lleft;
			else dierction=rright;
		}
		if(y1==y2){
			if(x1>x2) dierction=up;
			else dierction=down;
		}
		dis=(double)abs(h[x1]-h[x2])/2+(double)abs(w[y1]-w[y2])/2;
		mid_x=(double)(x1+x2)/2;
		mid_y=(double)(y1+y2)/2;
	}
	//在(x1,y1)与(x2,y2)两点中点,方向朝向direction,路段的半程长度为dis 
};//注意!x1,x2,y1,y2表示的是路口下标编号 
//订单信息-while(q--)实时处理 
int q;double ans;//订单数量q
//关于红绿灯系统:
#if 0/* 
g为南北向绿灯持续时间,r为东西向绿灯持续时间
变灯周期是(g+r)
==> 南北向绿灯时间区间 [n*(g+r),n*(g+r)+g )  左闭右开,n∈Z* 
	东西向绿灯时间区间 [n*(g+r)+g,(n+1)*(g+r) ) 左闭右开,n∈Z* 
==> ti时刻红绿灯状态:
	if(ti%(g+r)<g) 南北向绿灯,东西向红灯
	else if(ti%(g+r)>=g) 南北向红灯,东西向绿灯 
==>等待变灯时间:
	南北向绿灯时:dhd=g-ti%(g+r) 
	东西向绿灯时:dhd=g+r-ti%(g+r) */ 
#endif
int g[105][105],r[105][105];
inline bool pd(int ti,int idx,int idy){//true=南北向,false=东西向
	if(ti%(g[idx][idy]+r[idx][idy])<g[idx][idy])  return true;//南北向绿灯,东西向红灯
	else if(ti%(g[idx][idy]+r[idx][idy])>=g[idx][idy]) return false;//南北向红灯,东西向绿灯 
}
//??我也不知道该怎么写的最短路 //100的数据量,果断暴力吧 
//g表示南北,r表示东西;;w是南北,h是东西;x的+-是南北移动,y的+-是东西移动 
bool vis[105][105][105][105];
double bestway(s1 s,s1 e,double total_ti){
	//s是当前位置,e是目标位置 
//	cout<<"When in"<<total_ti<<"s,Mark_point:("<<s.x1<<","<<s.y1<<")("<<s.x2<<","<<s.y2<<")\n";
//----------------------------------------------	
	//终点判定 if(s==e) return 0;
	if(s.x1==e.x1&&s.x2==e.x2&&s.y1==e.y1&&s.y2==e.y2){
//		cout<<"~~!!victory!!~~"<<"this mehod costs"<<total_ti<<endl<<endl;
		return 0;
	}
//----------------------------------------------
	//第一步,只能无脑往前开,先开到路口再说 
	vis[s.x1][s.y1][s.x2][s.y2]=true;
	double res=(1<<30);//从当前位置出发,到达终点的最短时间 
	double awsl=s.dis;
	//awsl表示从当前位置到达下一中点的时间 = 当前半条路+下一个半条路+等红灯的时间 
	//从当前位置到达nex的时间=half+awsl 
//------------------------------------------------
	//判断-在ans+total_ti时的 红绿灯情况 
	//此时到达(s.x2,s.y2)的十字路口 	 
	double dhd=0; s1 nex;//nex表示下个位置 ; dhd=如果要灯红灯,需要的等待时长
	int ti=floor(ans+total_ti+awsl);double sb=ans+total_ti+awsl-ti; 
	if(pd(ti,s.x2,s.y2)){//南北向绿灯 
		if(s.dierction==lleft||s.dierction==rright)//让东西向等待
			dhd= g[s.x2][s.y2]-ti%(g[s.x2][s.y2]+r[s.x2][s.y2]) ;
	}else{//东西向红灯 
		if(s.dierction==up||s.dierction==down)//让南北向等待 
			dhd=g[s.x2][s.y2]+r[s.x2][s.y2]-ti%(g[s.x2][s.y2]+r[s.x2][s.y2]) ;
	}dhd+=sb;
//	cout<<"等红灯:"<<dhd<<endl;
//-----------------------------------------------
	//合法情况下,有四种选,即四个方向 
	//剪枝+防止死循环:不绕圈就是省时间,不能重复到达同一pos 
	//无理由剪枝:不绕路 
	//to-lleft
	if(s.y2-1>=1&&s.y2>e.mid_y){
		nex=(s1){s.x2,s.y2,s.x2,s.y2-1,0};nex.dir();
		//如果没有兜圈 
		if(!vis[nex.x1][nex.y1][nex.x2][nex.y2]){
			awsl+=nex.dis;
			//判断转向并计算路口等待时间 
			if(s.dierction==nex.dierction||(s.dierction+3)%4==nex.dierction) awsl+=dhd; //直走或左转
			if((s.dierction+2)%4==nex.dierction||(s.dierction+1)%4==nex.dierction)	awsl+=0;//右转或掉头 
			//到达下一个中点,继续深搜 
			res=min(res, awsl+bestway(nex,e,total_ti+awsl));//ti=awsl-s.dis=等灯和半条路 
			//回溯 
			awsl-=nex.dis;
			if(s.dierction==nex.dierction||(s.dierction+3)%4==nex.dierction) awsl-=dhd; //直走或左转
			if((s.dierction+2)%4==nex.dierction||(s.dierction+1)%4==nex.dierction)	awsl-=0;//右转或掉头 
		}
	}
	//to-rright
	if(s.y2+1<=m&&s.y2<e.mid_y){
		nex=(s1){s.x2,s.y2,s.x2,s.y2+1,0};nex.dir();

		//如果没有兜圈 
		if(!vis[nex.x1][nex.y1][nex.x2][nex.y2]){
			awsl+=nex.dis;
			//判断转向并计算路口等待时间 
			if(s.dierction==nex.dierction||(s.dierction+3)%4==nex.dierction) awsl+=dhd; //直走或左转
			if((s.dierction+2)%4==nex.dierction||(s.dierction+1)%4==nex.dierction)	awsl+=0;//右转或掉头 
			//到达下一个中点,继续深搜 
			res=min(res, awsl+bestway(nex,e,total_ti+awsl));//ti=awsl-s.dis=等灯和半条路 
			//回溯 
			awsl-=nex.dis;
			if(s.dierction==nex.dierction||(s.dierction+3)%4==nex.dierction) awsl-=dhd; //直走或左转
			if((s.dierction+2)%4==nex.dierction||(s.dierction+1)%4==nex.dierction)	awsl-=0;//右转或掉头 
		}
	}
	//to-up
	if(s.x2-1>=1&&s.x2>e.mid_x){
		nex=(s1){s.x2,s.y2,s.x2-1,s.y2,0};nex.dir();
		//如果没有兜圈 
		if(!vis[nex.x1][nex.y1][nex.x2][nex.y2]){
			awsl+=nex.dis;
			//判断转向并计算路口等待时间 
			if(s.dierction==nex.dierction||(s.dierction+3)%4==nex.dierction) awsl+=dhd; //直走或左转
			if((s.dierction+2)%4==nex.dierction||(s.dierction+1)%4==nex.dierction)	awsl+=0;//右转或掉头 
			//到达下一个中点,继续深搜 
			res=min(res, awsl+bestway(nex,e,total_ti+awsl));//ti=awsl-s.dis=等灯和半条路 
			//回溯 
			awsl-=nex.dis;
			if(s.dierction==nex.dierction||(s.dierction+3)%4==nex.dierction) awsl-=dhd; //直走或左转
			if((s.dierction+2)%4==nex.dierction||(s.dierction+1)%4==nex.dierction)	awsl-=0;//右转或掉头 
		}
	}
	//to-down  
	if(s.x2+1<=n&&s.x2<e.mid_x){
		nex=(s1){s.x2,s.y2,s.x2+1,s.y2,0};nex.dir();
		//如果没有兜圈 
		if(!vis[nex.x1][nex.y1][nex.x2][nex.y2]){
			awsl+=nex.dis;
			//判断转向并计算路口等待时间 
			if(s.dierction==nex.dierction||(s.dierction+3)%4==nex.dierction) awsl+=dhd; //直走或左转
			if((s.dierction+2)%4==nex.dierction||(s.dierction+1)%4==nex.dierction)	awsl+=0;//右转或掉头 
			//到达下一个中点,继续深搜 
			res=min(res, awsl+bestway(nex,e,total_ti+awsl));//ti=awsl-s.dis=等灯和半条路 
			//回溯
			awsl-=nex.dis;
			if(s.dierction==nex.dierction||(s.dierction+3)%4==nex.dierction) awsl-=dhd; //直走或左转
			if((s.dierction+2)%4==nex.dierction||(s.dierction+1)%4==nex.dierction)	awsl-=0;//右转或掉头 
		}
	}
	//回溯 
	vis[s.x1][s.y1][s.x2][s.y2]=false;//防止转圈
//	cout<<"回溯记录:When in"<<total_ti<<"s,Mark_point:("<<s.x1<<","<<s.y1<<")("<<s.x2<<","<<s.y2<<")\n";
//	cout<<"res="<<res<<endl;
	return res;
}


int main(){
	//一堆输入 
	n=read(),m=read();
	for(int i=2;i<=n;i++) h[i]=read();//东西 
	for(int i=2;i<=m;i++) w[i]=read();//南北 
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			g[i][j]=read();
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			r[i][j]=read();

	s1 pos=(s1){read(),read(),read(),read(),0};pos.dir();
	s1 home=pos;
	//开始跑单 
	q=read();double ls;
	while(q--){ 	s1 S,E;
		S=(s1){read(),read(),read(),read(),0};S.dir();
		E=(s1){read(),read(),read(),read(),0};E.dir();

		ls= bestway(pos,S,0);//从当前位置到接单位置 
		ans+=ls;
//		cout<<"本阶段:"<<ls<<endl<<"ans="<<ans<<endl<<endl;
		ls = bestway(S,E,0);//送单
		ans+=ls;
//		cout<<"本阶段:"<<ls<<endl<<"ans="<<ans<<endl<<endl<<"-------------"<<endl;
		
		pos=E;
	} 
//	cout<<"-----------------START-----------------------"<<endl;
	ls=bestway(pos,home,0);ans+=ls;//回家 
//	cout<<"本阶段:"<<ls<<endl<<"ans="<<ans<<endl<<endl;
	printf("%.1f",ans);
	return 0;
} 
/*

正解

第一遍做的时候读题模拟就差点累死,所以无脑dfs了
写完才发现,红绿灯时间直接在最短路的时候算在边权里就好了,于是有了下面这个spfa
建图+spfa最短路(AC)(100分)://建图思路都在备注里

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
inline int read(){
	char ch=getchar();int sum=0;
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9')
		sum*=10,sum+=ch-'0',ch=getchar();
	return sum;
} 
//数据范围:n,m<=100; q<=30; h,w<=1e5; g,r<=1000; 
#include<cmath>
//建点 
int up=0,rright=1,down=2,lleft=3;
int h[105],w[105];int n,m;
const int N=100*100*2+5;//n*m的网格,正方向算两个点==>空间n*m*2; 
struct s1{
	int x1,y1,x2,y2;
	int dierction;//去往方向 
	double dis;//该路段的半程距离 
	void dir(){//判断dierction和dis 
		if(x1==x2){
			if(y1>y2) dierction=lleft;
			else dierction=rright;
		}
		if(y1==y2){
			if(x1>x2) dierction=up;
			else dierction=down;
		}
		dis=(double)abs(h[x1]-h[x2])/2+(double)abs(w[y1]-w[y2])/2;
	}
}pos[N];int tot;
int id[105][105][105][105];//查询对应点的id 
inline void add_pos(int x1,int y1,int x2,int y2){
	pos[++tot]=(s1){x1,y1,x2,y2,0,0};pos[tot].dir();
	id[x1][y1][x2][y2]=tot;
}

//建边
const int M=N*4;//每个点最多连出直走、左转、右转、调头四个边==>空间N*4 
bool ahead=true,turnleft=true,turnright=false,reback=false;//直走左转要等灯,右转掉头不需要 
struct s2{
	int to,nex;double va;
	bool turn;//记录转向 即是否需要等待红绿灯 
}edge[N*4];int head[N],ecnt;//不同时间点的红绿灯情况不同,因而边权在变化,不做定义 
inline void add(int x,int y,bool turn){//va=两边的半程的加和 
	edge[++ecnt]=(s2){y,head[x],pos[x].dis+pos[y].dis,turn};head[x]=ecnt;
}
bool zx(int x,int y){//判断从点x到点y的转向
	if(pos[x].dierction==pos[y].dierction||(pos[x].dierction+3)%4==pos[y].dierction) return true; //直走或左转
	if((pos[x].dierction+2)%4==pos[y].dierction||(pos[x].dierction+1)%4==pos[y].dierction) return false;//右转或掉头
}

//处理红绿灯系统:
#if 0/* 
g为南北向绿灯持续时间,r为东西向绿灯持续时间
变灯周期是(g+r)
==> 南北向绿灯时间区间 [n*(g+r),n*(g+r)+g )  左闭右开,n∈Z* 
	东西向绿灯时间区间 [n*(g+r)+g,(n+1)*(g+r) ) 左闭右开,n∈Z* 
==> ti时刻红绿灯状态:
	if(ti%(g+r)<g) 南北向绿灯,东西向红灯
	else if(ti%(g+r)>=g) 南北向红灯,东西向绿灯 
==>等待变灯时间:
	南北向绿灯时:dhd=g-ti%(g+r) 
	东西向绿灯时:dhd=g+r-ti%(g+r) */ 
#endif
#define start_time ans
int g[105][105],r[105][105];
double dis[N],ans;//dis[i]起点到i的最短时间,start_time从起点开始走的时刻,即ans
bool pd_light(int ti,int idx,int idy){//true=南北向,false=东西向
	if(ti%(g[idx][idy]+r[idx][idy])<g[idx][idy])  return true;//南北向绿灯,东西向红灯
	else if(ti%(g[idx][idy]+r[idx][idy])>=g[idx][idy]) return false;//南北向红灯,东西向绿灯 
}
double dhd(int x,int y){//从x点到y点,到十字路口的时刻是ti+sb时刻需要等红灯的时长 
	double dh=0; s1 nex;//nex表示下个位置 ; dhd=如果要灯红灯,需要的等待时长
	double ls=start_time+dis[x]+pos[x].dis;//当前时刻=到达点x的时刻+x到路口的时刻
	double sb=ls-floor(ls);
	int ti=floor(ls);
	//此时十字路口位置: (pos[x].x2,pos[x].y2 )
	if(pd_light(ti,pos[x].x2,pos[x].y2)){//南北向绿灯 
		if(pos[x].dierction==lleft||pos[x].dierction==rright)//让东西向等待
			dh= g[pos[x].x2][pos[x].y2]-ti%(g[pos[x].x2][pos[x].y2]+r[pos[x].x2][pos[x].y2]) ;
	}else{//东西向红灯 
		if(pos[x].dierction==up||pos[x].dierction==down)//让南北向等待 
			dh=g[pos[x].x2][pos[x].y2]+r[pos[x].x2][pos[x].y2]-ti%(g[pos[x].x2][pos[x].y2]+r[pos[x].x2][pos[x].y2]) ;
	} return dh+sb;//+sb的正确性:不可能两个方向都是红灯或都是绿灯 
}
//bfs最短路
#include<queue>
bool vis[N];//dis[to]=min(dis[to],dis[pos]+edge[i].va+dhd(pos,to))
inline double bfs(int S,int T){
	for(int i=0;i<=tot;i++) dis[i]=(1<<30);
	memset(vis,0,sizeof(vis));
	queue<int>q;q.push(S);vis[S]=true;dis[S]=0;
	while(!q.empty()){
		int pos=q.front();q.pop();vis[pos]=false;
		//cout<<S<<":"<<pos<<endl;
		for(int i=head[pos];i;i=edge[i].nex){
			int to=edge[i].to;double va;
			if(zx(pos,to)) va=edge[i].va+dhd(pos,to);//直走、左转等红灯 
			else va=edge[i].va;//右转,调头直接走
			
			if(dis[to]>dis[pos]+va){
				dis[to]=dis[pos]+va;
				if(!vis[to]){
					q.push(to);
					vis[to]=true;
				}
			} 
		}
	}
	return dis[T];
} 


int home;
int main(){
	//读入 
	n=read(),m=read();
	for(int i=2;i<=n;i++) h[i]=read();//东西 距离 
	for(int i=2;i<=m;i++) w[i]=read();//南北 距离
	//建点 
	for(int x1=1;x1<=n;x1++)
		for(int y1=1;y1<=m;y1++){
			if(x1+1<=n) add_pos(x1,y1,x1+1,y1);//down
			if(x1-1>=1) add_pos(x1,y1,x1-1,y1);//up
			if(y1+1<=m) add_pos(x1,y1,x1,y1+1);//rright
			if(y1-1>=1) add_pos(x1,y1,x1,y1-1);//lleft
		}
	//建边
	for(int i=1;i<=tot;i++){
		int x1=pos[i].x1,x2=pos[i].x2,y1=pos[i].y1,y2=pos[i].y2;int to; 
		to=id[x2][y2][x2+1][y2]; if(to) add(i,to,zx(i,to));
		to=id[x2][y2][x2][y2+1]; if(to) add(i,to,zx(i,to));
		to=id[x2][y2][x2-1][y2]; if(to) add(i,to,zx(i,to));
		to=id[x2][y2][x2][y2-1]; if(to) add(i,to,zx(i,to));
	} 
	//红绿灯信息 
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			g[i][j]=read();
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			r[i][j]=read();
	//home
	home=id[read()][read()][read()][read()];
	//跑单
	int q=read();int pp=home;double ls;//pp为当前位置 ls调试用的,可忽略 
	while(q--){
		int s=id[read()][read()][read()][read()];//接单起点 
		int e=id[read()][read()][read()][read()];//送单终点 
		ls=bfs(pp,s);ans+=ls;//当前位置到接单位置
//		cout<<"本阶段:"<<ls<<endl<<"ans="<<ans<<endl<<endl;
		ls=bfs(s,e); ans+=ls;
//		cout<<"本阶段:"<<ls<<endl<<"ans="<<ans<<endl<<endl;
		pp=e;
//		cout<<"还剩"<<q<<"单:"<<ans<<endl;
	}
	ls=bfs(pp,home);ans+=ls;//回家 
//	cout<<"本阶段:"<<ls<<endl<<"ans="<<ans<<endl<<endl;
	printf("%.1f",ans);		return 0;					
} 

贪心:

根据生活常识朴素地认为,如果重复走一个点,那一定是绕路,没有意义。
所以推证 到达任意地点的最短路有且只有一个。根据BFS逻辑更新即可。

  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

GoesM

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值