题目
题目简介
一个需要等红绿灯的最短路设计问题
法一: 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逻辑更新即可。