icpc 2018 World Finals Problem A-Catch the Plane

一、题目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Your plane to the ICPC Finals departs in a short time, and the only way to get to the airport is by bus.
Unfortunately, some of the bus drivers are considering going on strike, so you do not know whether
you can get to the airport on time. Your goal is to plan your journey in such a way as to maximize the
probability of catching your plane.
You have a detailed map of the city, which includes all the bus stations. You are at station 0 and the
airport is at station 1. You also have a complete schedule of when each bus leaves its start station and
arrives at its destination station. Additionally, for each bus you know the probability that it is actually
going to run as scheduled, as opposed to its driver going on strike and taking the bus out of service.
Assume all these events are independent. That is, the probability of a given bus running as planned does
not change if you know whether any of the other buses run as planned.
If you arrive before the departure time of a bus, you can transfer to that bus. But if you arrive exactly
at the departure time, you will not have enough time to get on the bus. You cannot verify ahead of time
whether a given bus will run as planned – you will find out only when you try to get on the bus. So if
two or more buses leave a station at the same time, you can try to get on only one of them.
题目大意:
1
2
3
4
5
6
7
8
你去ICPC决赛的飞机很快就要起飞了,而到达机场的唯一方法就是乘巴士。不幸的是,一些巴士司机正在考虑罢工,所以你不知道你能否按时到达机场。你的目标是用一种方式来规划你的旅程,使你赶上飞机的可能性最大化。

你有一张详细的城市地图,包括所有的公交车站。你在0站,机场在1站。你也有一个完整的时间表,时间表上显示了每辆巴士离开它的起点和到达它的目的站得时间。
此外,对于每一辆巴士,你知道它实际上会按照计时间表出发的概率,由此也就可以知道它的司机罢工并停止服务的概率。假设所有这些事件都是独立的。
也就是说,如果你知道了任何其他巴士是否按照计划运行,并不会改变给定的一个巴士按时间表运行的概率。

如果你在一辆巴士出发前到达,你可以换乘那辆巴士。但是如果你正好在巴士离开的那个时间点到达,你就没有足够的时间上车。你无法提前验证给定的总线是否会按照计划运行,只有当你决定上车时才会发现。
因此,如果两辆或两辆以上的公共汽车同时离开一个车站,你只能尝试乘坐其中一辆。
输入样例:

input

输入描述:

输入的第一行包含两个整数m(1≤m≤$10^6$)和n(2≤n≤$10^6$),分别表示巴士的数量和城市里公交站的数量。

第二行包含一个整数k(1≤k≤$10^{18}$),表示你必须到达机场的时间。

接下来的m行是对m个巴士的描述。其中每一行包含整数a和b(0 ≤ a,b < n,a $\neq$ b),分别表示汽车的起始站和目的地站。接下来是整数s和t(0 ≤ s < t ≤ k)),表示从a车站出发的时间和到达b车站的时间。每一行的最后一个值p(0≤p≤1,小数点后最多10位数),表示巴士按计划出发的概率。

输出:

按最优路线,给出你赶上飞机的概率。输出结果误差不超过 $10^{−6}$

二、分析

以输入样例1(输出为0.3124)为例子说明,下面是它对应的巴士时间表:

班次 出发站 目的站 出发时间 出发概率 到达时间
(1) 0 1 0 20% 900
(2) 0 2 100 100% 500
(3) 2 1 500 100% 700
(4) 2 1 501 10% 701
(5) 0 3 200 50% 400
(6) 3 1 500 10% 800
(7) 3 0 550 90% 650
(8) 0 1 700 10% 900

看一下上面这个表格,它列出了几条公交路线的起点和终点,以及出发和到达的时间。并且在出发时间旁边标上了这条路线正常运行的概率,如果没有标注概率则表明它的100%正常运行的。

一开始我们在站点0,假设我们上了第(1)班车。如果它正常运行,那就直接到机场了(站点1),其他的也就不用考虑。如果这趟车没有正常出发,就要考虑下面的换乘方案了。

此时可以考虑乘第(2)班车从站点0到站点2,再从站点2坐到站点1。首先第(2)班车肯定会正常出发,但是到达站点2的时间是500,再看一下第(3)班车,出发时间是500,所以来不及赶上第(3)班车。而第(4)班车出发时间是501,可以赶得上,但是正常出发的概率只有10%。这个方案我们保留一下。看看有没有别的方案。

考虑乘第(5)班车从站点0到站点3,出发的概率是50%,到达站点3的时间是400。第(6)班车是从3到1,出发时间是500,时间上可以赶得上,出发概率是10%,如果正常出发则直接到达机场。如果没有正常出发,只能在站点3乘坐第(7)班车回到站点0,然后乘坐第(8)班车到达机场,并且这两辆车正常出发的概率分别是90%和10%。

按照上面的分析可以画出如下的树状图,其中出发车站是第0站,里面是它的编号。下面的每个矩形结点表示各个路线,每个矩形有两部分,第一部分的键值对分别表示这条路线巴士出发站点和目的站点,以及正常出发的概率。第二部分表示发车失败的概率。例如,对照上面的发车时刻表,这里从0站点以0.2的概率乘坐第(1)班车达到站点1,而有0.8的概率是其他情况。下图所示:

tree1

根据这个树状图形计算最终的输出结果:

1
p = max{0.2+0.8*1*(0.1), 0.2+0.8*[0.5*(0.1+0.9*0.9*0.1+0.5*0.1), 0.2+0.8*0.1] = 0.3124}

这里省略了乘以0的情况(肯定不会达到机场的情况)。其中每个矩形结点的两部分(包含子结点)的概率需要相加。考虑到正常发车的话左边部分的子结点也最多只能有一个;而如果发车失败的话,矩形结点右边部分可能会有多个子结点(表示不同的换乘方法),此时要拆开来计算对比每种情况的概率大小,最终取概率最大值的那种情况。

同理分析输入样例2可以得到:

1
p = max{0.5+0.5*0.4, 0.5+0.5*0.2, 0.5+0.5*0.4, 0.5+0.5*0.2} = 0.7}

三、Python 代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
from decimal import Decimal

pathCount = 0
pathList = []
stationCount = 0
maxArriveTime = 0

# 节点类,表示路线
class Node:
pT = 0 # 正常发车的概率
pF = 1 - pT # 罢工的概率
startStation = 0 # 路线出发站点
endStation = 0 # 路线目的站点
startTime = 0 # 出发时间
endTime = 0 # 到达时间
leftNode = None # 左子树
rightNodes = [] # 右子树

def __init__(self, startStation, endStation, startTime, endTime, pT):
self.startStation = int(startStation)
self.endStation = int(endStation)
self.startTime = int(startTime)
self.endTime = int(endTime)
self.pT = Decimal(pT)
self.pF = Decimal(1.0) - self.pT

def prt(self):
print(self.startStation, self.endStation, self.startTime, self.endTime, self.pT, self.pF)

def printNodes(list):
for node in list:
node.prt()

# 加载输入数据 ./input1.txt
def loadInput(path):
pathList = []
f = open(path)
ln = 0
for line in f:
if ln == 0:
pathCount, stationCount = line.split()
else:
if ln == 1:
maxArriveTime = line
else:
startStation, endStation, startTime, endTime, pT = line.split()
node = Node(startStation, endStation, startTime, endTime, pT)
pathList.append(Node(node.startStation, node.endStation, node.startTime, node.endTime, node.pT))
ln = ln + 1
f.close()
return pathCount, stationCount, pathList, maxArriveTime

# 查找以 startStation 为出发站的路线
def findNodeStartAs(startStation, lastEndTime):
stations = []
for node in pathList:
if (node.startStation == startStation and node.startTime > lastEndTime):
stations.append(node)
return stations

# 根据时刻表构建树
def buildTree(node):
if node:
# 回退找出以上一个出发站为起点的所有路线,全部添加到右子树
rightStations = findNodeStartAs(node.startStation, node.startTime)

# 找出以从当前车站为起点的所有路线选出第一个作为左子树
leftStations = findNodeStartAs(node.endStation, node.endTime)
if len(leftStations) > 0:
leftNode = leftStations[0]

# 不管概率是多少,第一时间出发的车设置为 leftNode
if node.endStation != 1:
node.leftNode = leftNode
# 递归构建左子树
buildTree(node.leftNode)

# 发车概率小于1,需要考虑其他情况,所以要添加右子树。
if node.pT < 1:
node.rightNodes = rightStations
# 递归构建右子树
for rnode in node.rightNodes:
buildTree(rnode)

# 计算最大概率
def caculateProbability(node):
if node == None:
return 0

# 递归求出右子树的最大概率值,也就是发车失败换乘情况的最大概率值
rightNodesP = []
rightP = 0;
if node.rightNodes and len(node.rightNodes) > 0:
for rnode in node.rightNodes:
rightNodesP.append(caculateProbability(rnode))
rightP = max(rightNodesP)

# 递归求出左子树的最大概率值,也就是正常发车的概率值
leftP = 1;
if node.leftNode:
leftP = caculateProbability(node.leftNode)
else:
leftP = 1

# 左子树概率加右子树概率
return node.pT * leftP + node.pF * rightP

# Step1: 读取输入样例
pathCount, stationCount, pathList, maxArriveTime = loadInput("./input1.txt")

# Step2: 构造一个虚拟的初始节点
initNode = Node(0, 0, 0, -1, 1)

# Step3: 执行构造树的方法
buildTree(initNode)

# Step4: 执行计算概率的方法
print("样例1输出:",caculateProbability(initNode))

执行之后输出:

1
0.3124

验证输入样例2的输出为 0.7,也没问题。


THE END.

评论