SGU 103 Traffic Lights
题目大意:
Dingiville 城市的交通规则非常奇怪,城市公路通过路口相连,两个不同路口之间最多只有一条直达公路。公路的起止点不会是同一路口。在任意一条公路上顺不同方向走所需 时间相同。每一个路口都有交通灯,这些交通灯在某一时刻要么是蓝色,要么是紫色。同一个灯上2个颜色的维持时间受到定期调控,总是蓝色持续一段时间,紫色 持续一段时间。交通规则规定两个路口可以通车仅当公路两边交通灯的颜色相同(也就是说只要你在A城市看见A与B的交通灯颜色相同,那么你就可以走上A-B 这条路并到达B点)。交通工具可以在路口等候。现在你有这个城市的地图,包含:
• 通过所有公路需要的时间(整数)
• 每个路口交通灯两种颜色的持续时间(整数)
• 每个路口交通灯的初始颜色以及初始颜色的持续时间(整数).
你的任务是找到一条从起点到终点的最快路径,当有多条这样的路径存在时,你只需输出任意一条即可。
输入
第一行包含两个整数: 起点和终点的编号. 第二行包含两个整数: N, M. 接下来 N 行描述 N 个路口的情况.
第 (i+2) 行 是 i 号路口的信息: Ci, riC, tiB, tiP 。其中字符 Ci 要么是“B”代表蓝色,要么是“P”代表紫色,表示 i 的 起始颜色. riC, tiB, tiP 分别表示初始颜色,蓝色,紫色的持续时间, 1 ≤ tiC ≤ 100 . 1 ≤ riC ≤ tiC 。最后的 M 行表示关于 M 条公路的信息。包含: i, j, lij 。i 和 j 号路口被这条路连接. 2 ≤ N ≤ 300 表示路口数. 路口从 1 到 N 编号. 1 ≤ M ≤ 14000 公路从 1 到 M 编号. 1 ≤ lij ≤ 100 表示 lij 是通过i到j这条公路需要的时间。
输出
如果存在最短路径:
• 第一行输出最短时间。
• 第二行是一个对应第一行答案的路口编号表,从起点到终点输出路口编号,数字之间用空格隔开。
如果不存在:
• 输出“0”.
样例 输入
1 4
4 5
B 2 16 99
P 6 32 13
P 2 87 4
P 38 96 49
1 2 4
1 3 40
2 3 75
2 4 76
3 4 77
输出
127
1 2 4
这显然是一道变形的无向图最短路,只有两边的灯颜色一样的时候才可以通行(只需考虑出发时刻,不用考虑到达时刻),这里我使用的是SPFA。
由于时间只会增加不会减少,所以对于每个点的最短路依然满足贪心原则(即一个点的最短路由另一个点的最短路推出)
那么我们只需更改一下SPFA的松弛操作,就可以得到正解。
思考:对于接下来的地方,要么现在就出发,要么等一阵子出发。
那么等待时间呢?
仔细思考就会发现,等待时间只有四种。即当前点的下次变灯时间,当前点的下下次变灯时间,目标点的下次变灯时间,目标点的下下次变灯时间(因为每个点只有两种灯)。
在我的程序中我设置了一个求对应时间对应地点的灯和下次变灯时间的函数。
加上马上出发,我们一共考虑了五种情况。(PS:据说有一种状况是多余的,这个还有待向大神考证,增加一种情况并不会导致超时)
判断无解的话与标准SPFA一样。
记录路径的方法与标准SPFA一样(使用rank数组)。
值得注意的一点:队列不要开小了,如果交上去发现自己MLE且内存崩到200000KB以上,则证明你数组开小了。
对于无向图,我使用的是邻接矩阵(不会超时),如果要写邻接表或者更高端方法的大神,可以参考其他大神的博客。
下面附上我的代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <math.h>
struct j
{
int slight; //开始灯颜色
int time[3]; //0—开始剩余时间,1—蓝灯时间,2—紫灯时间
}junction[301]; //连接点
struct ne
{
int light;
int next;
}turni,turnj;
int group[301][301]; //邻接矩阵
int ans[301],rank[301];
int line[1001],l,r;
bool check[301];
int start,end;
int n,m;
void print(int i) //输出路径
{
if (i!=start)
print(rank[i]);
printf("%d ",i);
return ;
}
int wait[4]; //等待时间
struct ne light(int i,int time) //计算time时刻i点的灯和下次变灯时间
{
struct ne re;
if (time<junction[i].time[0])
{
re.light=junction[i].slight;
re.next=junction[i].time[0]-time;
return re;
}
time-=junction[i].time[0];
time%=(junction[i].time[1]+junction[i].time[2]);
if (time>=junction[i].time[3-junction[i].slight])
{
re.light=junction[i].slight;
re.next=junction[i].time[junction[i].slight]-(time-junction[i].time[3-junction[i].slight]);
}
else
{
re.light=3-junction[i].slight;
re.next=junction[i].time[3-junction[i].slight]-time;
}
return re;
}
void spfa(int i)
{
int j,k;
for (j=1;j<=n;j++)
if (group[i][j])
if (light(i,ans[i]).light==light(j,ans[i]).light) //直接出发
if (ans[j]>ans[i]+group[i][j])
{
ans[j]=ans[i]+group[i][j];
rank[j]=i;
if (!check[j])
line[++r]=j;
check[j]=true;
}
else
{}
else //计算四次等待时间
{
turni=light(i,ans[i]);
turnj=light(j,ans[i]);
wait[0]=turni.next;
wait[1]=turnj.next;
wait[2]=turni.next+junction[i].time[3-turni.light];
wait[3]=turnj.next+junction[j].time[3-turnj.light];
for (k=0;k<4;k++) //对四次等待时间的扩展
if (light(i,wait[k]+ans[i]).light==light(j,wait[k]+ans[i]).light)
if (ans[j]>wait[k]+ans[i]+group[i][j])
{
ans[j]=wait[k]+ans[i]+group[i][j];
rank[j]=i;
if (!check[j])
line[++r]=j;
check[j]=true;
}
}
check[i]=false; //清除标记
for (;l<=r;) //根据队列扩展
{
l++;
spfa(line[l-1]);
}
return ;
}
void init()
{
int i,a1,a2,a3,a4;
char c;
scanf("%d %d\n",&start,&end);
scanf("%d %d\n",&n,&m);
for (i=1;i<=n;i++)
{
scanf("%c",&c);
a1=c;
scanf("%d %d %d\n",&a2,&a3,&a4);
if (a1=='B')
junction[i].slight=1;
else
junction[i].slight=2;
junction[i].time[0]=a2;
junction[i].time[1]=a3;
junction[i].time[2]=a4;
ans[i]=2147483647;
}
ans[start]=0;
for (i=1;i<=m;i++)
{
scanf("%d%d%d",&a1,&a2,&a3);
group[a1][a2]=a3;
group[a2][a1]=a3;
}
l=1; r=0;
spfa(start);
if (ans[end]!=2147483647)
{
printf("%d\n",ans[end]);
print(end);
}
else
printf("0");
return ;
}
int main()
{
init();
return 0;
}