题目如下:
试题编号: | 202206-4 | |||||||||||||||||||||
试题名称: | 光线追踪 | |||||||||||||||||||||
时间限制: | 2.0s | |||||||||||||||||||||
内存限制: | 512.0MB | |||||||||||||||||||||
问题描述: | 问题描述光线追踪是计算机图形学领域的一个重要算法,其原理是追踪一束从光源发出的光,经过不同的反射面,最终到达摄像机处的过程。 在这道问题中,你需要实现一段程序来处理一个简易的光线追踪模型。 在平面中有一些反射面,为方便起见,我们设这些反射面都是线段,与坐标轴成 45 度角摆放,且两个端点的坐标均为整数。为进一步简化问题,我们假设所有的反射表面都是镜面反射。任何一束光线照射到反射面上(为避免讨论,假设反射面不含端点)时,都会改变方向为相应的镜面反射方向。注意,反射面的两侧都可以反射光线。 平面中还有一些激光光源,每个光源位于一个坐标为整数的点上,会向某个水平或竖直的方向发射一定强度的激光。 所有的反射面都不是完美的,每个反射面有一个折损系数 a ,当强度为 I 的光线照射上去时,反射光线的强度会变成 aI 。为了便于处理,你可以认为所有反射面的材质均不算太好也不算太糟,因此所有的 a 均在 0.2∼0.8 的范围内。 在一些超高速摄影问题中,有时甚至连光速都要考虑在内。在这个问题中,我们不妨假设激光在 1 单位时间内恰好移动 1 单位距离。然而,超高速摄影带来的往往是采样精度的损失,因此对于一束激光,最终采样到的光线强度都是向下取整后的数值。特别地,当一束激光的强度小于 1 时,认为其已经完全耗散。 问题的最开始,平面上没有反射面也没有光源。接下来你需要处理若干个操作,每个操作形如:
输入格式从标准输入读入数据。 第 1 行,一个正整数 m 表示操作的总数量。 接下来 m 行,每行描述一个操作,格式如题目描述。 其中,除了所有的 a 和 I 以外的输入均为绝对值不超过 109 的整数,其中 k 和 t 为正整数;a 和 I 均为小数点后不超过 6 位的正实数,其中 a 在 0.2∼0.8之间, I≤109。 输出格式输出到标准输出。 对于每个查询操作输出一行,3 个整数,形如 题目数据保证,你可以在计算时直接使用 64 位浮点数的运算和取整操作,而无需担心可能的精度误差问题。 样例输入 Data 样例输出 Data 数据范围
对于 100% 的数据,保证 m≤105 ,所有反射面的 |x1−x2| 之和不超过 3∗105 。 |
原题地址:CCF202206-4 光线追踪
先上结果:
3138781 | 光线追踪 | 08-12 23:14 | 8.513KB | PYTHON | 错误 | 0 | 10.04s | 26.77MB | ||
3138752 | 光线追踪 | 08-12 22:48 | 3.392KB | PYTHON | 正确 | 100 | 7.765s | 94.44MB | ||
3137684 | 光线追踪 | 08-11 12:26 | 4.179KB | PYTHON | 运行超时 | 35 | 运行超时 | 27.39MB |
首先说说35分的这个解法:分析题意,大概总结就是坐标系中有多个线段(镜子),并且这些线段不会相交,这些线段的斜率要么是1要么是-1,光从某一不位于任一线段上的点始发,要么横着要么竖着走,当光的路径碰到线段时,发生反射,改变行走方向。如此循环直到光强小于1或光的路程走完(时间相当于行走路程)
划重点:1、所有线段斜率要么1要么-1,且两两不相交
2、光的始发点不位于任何一个线段上
3、光的路径碰到线段时反射
4、光的走向要么横着要么竖着(在此基础上往前或往后)
5、终止条件:光强小于1或路径走完
基于此,我们简单的设计出解决方法:
1、光沿着行进方向前进
2、判断光的反射,若无反射,则算出终止点坐标,并置剩余路程为0;若有反射,算出光的反射点坐标,并更新光的出发点为反射点坐标,算出光的剩余路程,并计算反射后的光强
3、若光强小于1或剩余路程为0,结束循环,否则,,回到第一步
在这个基本思路中,可以发现重要步骤是第二步的判断光是否反射,也就是光在行进路径上是否会碰到某条线段,其本质是直线相交问题。
直接用直线方程联立求交点显然有点小题大做了,这里就需要注意问题的特殊了,即:线段的斜率要么为1要么-1,而光的行进路径要么横要么竖,所以直接将光的出发点坐标(横着走代入y,竖着走代入x)代入线段即可得反射点坐标,当然因为线段是有界的,所以在此之前要先判断光的行进方向会不会碰到线段(比如光横着走,则判断出发点坐标y是否在线段两端点坐标y1,y2之间)。
所以,根据上面的思路,会发现每次反射,都需要把所有的线段访问一遍,计算所有可能的反射点,然后取最近的反射点,也就是取与出发点坐标最近的反射点。
那么,有没有一种方法,在每次反射时,当判断出光会碰到线段后,直接得到反射点坐标及反射点与出发点的距离呢?
当然有,答案就是用x轴和y轴截距实现。
具体实现就是:在每条线段存储时,将该线段对应的x,y轴截距一并存储。然后在光反射时,以光的出发点计算其(过该点斜率为1或-1的直线)在x,y轴的截距(实际上只计算一个截距)。然后先判断光会不会碰到线段,若会,则直接用线段截距减去出发点截距则为反射点到出发点距离,保留此距离,以此遍历所有线段并保留最新距离,最后把最新距离加在出发点上即为寻找的反射点。
比如下面这样:
1 0 0 5 5 0.7 (1代表插入镜子操作,此镜子端点分别为(0,0)和(5,5),0.7是镜子反射率)
3 0 3 0 100 100 (3代表光行进操作,光始发点为(0,3),光始发方向为正x轴方向,初始光强100,剩余路程100)
执行1操作时,我们先计算出镜子的x,y轴截距及其斜率,结果为x0 = 0, y0=5, k = -1。
执行3操作时,光沿正x轴行进,所以我们先计算出光出发点的x轴截距(相当于过此点斜率为1和-1的直线的截距),这个截距有两个:
x(1) = -3 , x(-1) = 3
然后遍历所有镜子,当遍历到1操作插入的镜子时,先判断光会不会碰到镜子:即光始发点y(y = 3)坐标是否位于镜子两端点y坐标内(y1=0,y2=5),因0 <3<5,即y1 < y< y2,所以光如果一直往前一定会碰到镜子,此时直接用镜子x轴截距减去始发点x轴截距(镜子斜率为-1,所以选取x(-1)),即反射点到出发点距离loss = x0 - x(-1) = 3。
如此遍历所有镜子后,最后保留的最小loss就是实际反射点到出发点距离,反射点坐标即为(0+loss,3)
这里发现用截距判断好像也没省多少步骤,好像比直接求交点也就省了一步?
实际上由于每次反射都有遍历所有线段,每次遍历省一步,加起来也算省了好多步,不过这不是重点,重点是在截距判断方法的基础上,我们很容易就能想到优化方向。
先放一下未优化的截距判断法,提交结果35分。
def bdx(s,sx,sy):
if sx sx and s < sy:
return 1
else:return 0
else:
if s>sy and s= i[6]: #线段截距位于出发点截距左边,不会碰到(因为光向右走)
continue
if i[6] - x < loss: #保留最小loss,同时保留反射镜子信息
loss = i[6] - x
k = i[5]
a = i[4]
if d[4] < loss or a == 1.0: #没有反射,或光到不了反射点就走完路程
d[0] = d[0] + d[4]
return 0
d[3] = d[3] * a #更新光强
if d[3] < 1: #光强小于1,结束
d[0],d[1],d[3] = 0,0,0
return 0
if d[4] > loss: #剩余路程不为0,光继续前行
d[4] = d[4] - loss #重置剩余路程
d[2] = 2 - k #重置光行进方向
d[0] = d[0] + loss #重置出发点坐标
px(d) #递归调用,光继续前行
return 0
if d[4] == loss: #剩余路程为0,光刚好在反射点走完,结束
d[0] = d[0] + loss
return 0
elif d[2] == 1: #原理与d[2] == 0一致
loss = 1000000
k = 1
a = 1.0
for i in fsm.values():
if bdx(d[0], i[0], i[2]) == 0:
continue
if i[5] == 1:
x = d[1] - d[0]
else:
x = d[1] + d[0]
if x >= i[7]:
continue
if i[7] - x < loss:
loss = i[7] - x
k = i[5]
a = i[4]
if d[4] < loss or a == 1.0:
d[1] = d[1] + d[4]
return 0
d[3] = d[3] * a
if d[3] < 1:
d[0],d[1],d[3] = 0,0,0
return 0
if d[4] > loss:
d[4] = d[4] - loss
d[2] = 1 - k
d[1] = d[1] + loss
px(d)
return 0
if d[4] == loss:
d[1] = d[1] + loss
return 0
elif d[2] == 2:
loss = 1000000
k = 1
a = 1.0
for i in fsm.values():
if bdx(d[1], i[1], i[3]) == 0:
continue
if i[5] == 1:
x = d[0] - d[1]
else:
x = d[1] + d[0]
if x <= i[6]:
continue
if x - i[6] < loss:
loss = x - i[6]
k = i[5]
a = i[4]
if d[4] < loss or a == 1.0:
d[0] = d[0] - d[4]
return 0
d[3] = d[3] * a
if d[3] < 1:
d[0],d[1],d[3] = 0,0,0
return 0
if d[4] > loss:
d[4] = d[4] - loss
d[2] = 2 + k
d[0] = d[0] - loss
px(d)
return 0
if d[4] == loss:
d[0] = d[0] - loss
return 0
else:
loss = 1000000
k = 1
a = 1.0
for i in fsm.values():
if bdx(d[0], i[0], i[2]) == 0:
continue
if i[5] == 1:
x = d[1] - d[0]
else:
x = d[1] + d[0]
# print(x)
if x <= i[7]:
continue
if x - i[7] < loss:
loss = x - i[7]
k = i[5]
a = i[4]
# print(loss,k,a)
if d[4] < loss or a == 1.0:
d[1] = d[1] - d[4]
return 0
d[3] = d[3] * a
# print(d)
if d[3] < 1:
d[0],d[1],d[3] = 0,0,0
return 0
if d[4] > loss:
d[4] = d[4] - loss
d[2] = 1 + k
d[1] = d[1] - loss
px(d)
return 0
if d[4] == loss:
d[1] = d[1] - loss
return 0
sc = []
fsm = {}
m = int(input())
for i in range(m):
L = input().split()
if int(L[0]) == 1:
L = list(map(int, L[1:5])) + [float(L[-1])]
k = (L[1]-L[3])/(L[0]-L[2])
y0 = L[1] - k*L[0]
x0 = ((-1)*y0)/k
L = L+[k,x0,y0]
fsm[i + 1] = L
# print(L)
elif int(L[0]) == 2:
if int(L[1]) in fsm:
del fsm[int(L[1])]
else:
cs = list(map(int,L[1:4]))+[float(L[4]),int(L[5])]
if cs[3] < 1:
sc.append([0,0,0])
else:
px(cs)
sc.append([int(cs[0]),int(cs[1]),int(cs[3])])
for i in sc:
print(i[0],i[1],i[2])
接下来,先说说满分解方法(因为上面截距判断法的优化出了点小问题,众所周知,理论和实践是两码事)
满分解方法与截距法类似,用了截距法的优化原理,不过这里先说满分解方法也无所谓。
具体方法就是直接将所有反射点存储下来,也就是所有镜子上的点存储下来,因为每一个镜子上的点都是潜在的反射点。
没错,所有镜子上的点!
这里不得不再提一下本题的特殊性,题目中有一个条件很特殊:
对于 100% 的数据,保证 m≤105 ,所有反射面的 |x1−x2| 之和不超过 3∗105 。
注意,由于所有镜子不会相交,所以可以得出两个结论:
每一个镜子上的点唯一代表一个镜子,
所有镜子上点的总数和,等于所有镜子的长度和
所以,在本题中,所有潜在反射点的个数,不会超过3∗105
所以可以把所有点存一下而不用担心空间超限。
而这些点的存储不能随便存,它需要有一定规律来让我们更好的操作
这个规律当然是简单的排序啦!
具体的排序做法是:
首先建立两个字典:xj和yj
对每一个潜在反射点(x0,y0),在字典xj中,以x0为键,将y0添加到对应的值中,这个值是一个列表,列表的每一项元素yi都代表一个潜在反射点(x0,yi),列表中的yi按升序排好序。
在字典yj中,以y0为键,将x0添加到对应的值中,这个值是一个列表,列表的每一项元素xi都代表一个潜在反射点(xi,y0),列表中的xi按升序排好序。
这样,每一个反射点都存储了两次,为了更方便操作,我们可以将字典值列表中的元素yi(或xi)替换成一个子列表:[yi, i , k , a],其中,i代表这个潜在反射点(x0,yi)对应的镜子的插入操作序号,用于删除查找对应,k和a为镜子的斜率和反射率。
如此,在判断从出发点(x,y)出发的直线反射时,就可以直接查找对应的字典,然后用二分查找法找到反射点坐标。
这里先假设出发点位于某个镜子上,也就是已经经过一次反射(第一次反射可以稍微调整一下同理),找到这个出发点在字典中的位置,这个点前或后一个点(正方向走找后一个点,负方向走找前一个点)的点就是实际反射点(如果没有就是不反射)。
具体实现代码如下(100分):
def xl(x,ls): #二分法寻找x在ls中的位置
l0 = 0
le = len(ls) - 1
if ls[l0][0] >= x:
return 0
if ls[le][0] < x:
return le+1
if ls[le][0] == x:
return le
while le - l0 != 1:
lz = int((l0 + le) / 2)
if ls[lz][0] == x:
return lz
if ls[lz][0] < x:
l0 = lz
else:
le = lz
return le
def xs(i0,x1,y1,x2,y2,k,a): #插入字典,将所有i0操作待插入的镜子上的所有点插入xj,yj
# print(i0,x1,y1,x2,y2,k,a)
for x in range(x1+1,x2):
y = y1+k*(x-x1)
if x not in xj:
xj[x] = [[y,i0,k,a]]
else:
i1 = xl(y,xj[x])
xj[x].insert(i1,[y,i0,k,a])
if y not in yj:
yj[y] = [[x,i0,k,a]]
else:
i1 = xl(x,yj[y])
yj[y].insert(i1,[x,i0,k,a])
def dxs(i0,x1,y1,x2,y2,k): #删除镜子
for x in range(x1+1,x2):
y = y1+k*(x-x1)
xj[x].pop(xl(y,xj[x]))
yj[y].pop(xl(x,yj[y]))
if len(xj[x]) == 0:
xj.pop(x)
if len(yj[y]) == 0:
yj.pop(y)
def jmfs(i0,ke,k0,dc): #判断反射
loss = 1
k = 1
a = 1.0
i1 = -1
l = 0
if ke in dc:
di = dc[ke]
i1 = xl(k0,di)
l = len(di)
if i1 < l:
if di[i1][0] == k0:
i1 = i1+i0
else:
if i0 == -1:
i1 = i1 + i0
else:
if i0 == -1:
i1 = i1 + i0
if i1<0 or i1>=l:
return i0*loss, k, a
loss = di[i1][0] - k0
k = di[i1][2]
a = di[i1][3]
return loss,k,a
def fz(a,b): #光为到反射点就走完路程时更新终点
if b <0:
return -a
if b == 0:
return 0
return a
def sta(d): #一次行进
# print(d)
xloss,yloss = 0,0 #x,y轴方向的行进距离
if d[2] == 0:
xloss,k,a = jmfs(1,d[1],d[0],yj)
if d[2] == 1:
yloss,k,a = jmfs(1,d[0],d[1],xj)
if d[2] == 2:
xloss,k,a = jmfs(-1,d[1],d[0],yj)
if d[2] == 3:
yloss,k,a = jmfs(-1,d[0],d[1],xj)
if d[4] < abs(xloss+yloss) or a == 1.0: #不反射或为到反射点就走完路程
xloss = fz(d[4],xloss)
yloss = fz(d[4],yloss)
d[0] = d[0] + xloss
d[1] = d[1] + yloss
d[4] = 0
return 0
d[3] = d[3] * a
d[0] = d[0] + xloss
d[1] = d[1] + yloss
d[2] = turn[(d[2]+1)*k]
d[4] = d[4] - abs(xloss+yloss)
return 0
sc = []
fsm = {}
xj = {}
yj = {}
turn = {1:1,-1:3,2:0,-2:2,3:3,-3:1,4:2,-4:0} #反射转向字典,只有八种反射方式
m = int(input())
for i in range(m):
L = input().split()
if int(L[0]) == 1:
a = float(L[-1])
L = list(map(int, L[1:5]))
k = (L[1] - L[3]) / (L[0] - L[2])
if L[0]>L[2]:
L = [L[2],L[3],L[0],L[1]]
fsm[i + 1] = L
xs(i+1,L[0],L[1],L[2],L[3],int(k),a)
# print(L)
elif int(L[0]) == 2:
j0 = int(L[1])
j1 = fsm[j0]
k = (j1[1] - j1[3]) / (j1[0] - j1[2])
dxs(j0,j1[0],j1[1],j1[2],j1[3],int(k))
del fsm[j0]
print(fsm)
print(xj)
print(yj)
else:
cs = list(map(int, L[1:4])) + [float(L[4]), int(L[5])]
if cs[3] < 1:
sc.append([0, 0, 0])
else:
while cs[4] != 0:
# print(cs)
sta(cs)
if cs[3] < 1:
cs[0],cs[1],cs[3] = 0,0,0
break
sc.append([int(cs[0]), int(cs[1]), int(cs[3])])
for i in sc:
print(i[0],i[1],i[2])
以上方法其实还有明显可以优化的地方,当然,这个策略依然是用空间换时间,把排序的列表变成字典。
具体实现方法是:在原本的操作基础上,原来的xj和yj字典中每个x0,y0对应的值是一个二维列表,列表中每个元素代表一个反射点的信息。在这个存储方式下,我们发现每次判断反射时,依然要在对应的这个列表中查询光出发点位置,然后取光出发点前面或后面的点为反射点。
在这个操作中,我们可以将这个二维列表变成一个字典,用字典实现一个双链表形式,原来二维列表的结构是:
[...[yi, i , k , a],[yi, i , k , a],[yi+1, i , k , a]...]
变成字典后的结构是这样:
{...yi:[yi-1,yi+1,i,k,a]...}
也就是把列表元素[yi, i , k , a]变成键值对yi:[yi-1,yi+1,i,k,a],其中yi-1,yi+1分别为(x0,yi)的前一个点和后一个点。
这样在判断反射时就不需要进行查找操作,直接读取字典中对应的值,然后读取值中前一个和后一个点作为反射点,理论上来说这样可以将判断反射的时间复杂度降低一个量级,而且实际上空间也没有多很多。
当然,这样操作有利有弊,反射操作方便了,插入和删除操作就会复杂起来,大概估计,原来的插入、删除、反射操作时间复杂度都为O(log(n)),改用字典后反射操作时间复杂度为O(1),而插入和删除操作时间复杂度却变成了O(n)。
当然,我们可以对插入和删除操作进行特定的优化,这里不多叙述,感兴趣的朋友可以自己思考一下。
虽然这么用字典存储加大了插入和删除的复杂度,但由于每个删除、插入操作只执行一次,而每个反射操作却要反射不止一次,所有平均而言,我认为依然是用字典优化后的算法更优一点。
优化的具体方法我没有实践(代码太长是在不想写了),有兴趣的朋友可以实践一下,做出来可以踢我一下,让我看看我的理论是不是与实践结果一致。
好了,接下来进入最后一个问题,那就是对最开始的截距判断法优化。
优化原理如下:
我们回顾截距判断法的过程,其中一个关键步骤是:用镜子的截距,减去光出发点的坐标的投影截距,这个差作为loss(预计反射点到出发点的距离),对所有镜子都无差别做一次以上步骤,保留最小的loss对应的镜子作实际反射。
这里有一步对所有镜子都有判断反射,根据上面的满分题解,很容易想到这里用排序优化:
而当我们排序优化后,我们会发现效果超出了我们的预期(当然,是在理论上)
以光出发点(x,y)向x轴正方向行进为例,假设镜子(为了方便讨论,假设所有镜子斜率为1)x截距排序后列表为:[x1,x2,x3,x4,x5]
依然假设(x,y)是已经反射过一次的出发点,也就意味着(x,y)一定在某一面镜子上。
此时以斜率1算出(x,y)的x轴截距为x0,则x0一定等于截距列表中的一个值,假设x0 = x3.
则因为此时光是向x轴正方向行进,所以下一个反射点一定位于x3右侧,也就是从x4开始判断,若y位于x4对应镜子两端点的y1,y2之间,则显示光在x4处反射,反射点到出发点距离即:loss = x4-x0,若x4不反射,则继续向后访问,直到出现一个反射的镜子或访问到列表末尾。
接下来是重点,当反射结束后,若光需要继续反射前行,则需要将出发点更新为反射点,继续上面步骤。
但是,我们发现在上一步中已经访问过出发点(上一步的反射点位置),所以下一次反射便不需要查找出发点位置,直接用上一步的反射点位置反射,也就是说,在理想状况下,一个反射操作,不管需要多少次判断反射,都只需要一次二分查找!
而且,由于镜子斜率为1,所以镜子的x轴截距顺序列表刚好与y轴截距顺序列表相反,也就是我们只需要一个截距反射列表(斜率为-1时则完全相等),而这就更加方便下一次反射直接使用上一次反射点的位置作为下一次光出发点位置。
当然,这是在理想状态下,由于镜子斜率有1和-1两种,所以我们有两个x轴截距列表,反射时需要分别判断,这样直接使得问题复杂许多,但解决思路还是上面的方法,只是分类讨论要多一些判断操作。
但总体而言,此方法在理论上与中间100分题解时间复杂度相差不大,甚至我个人认为排序后的截距判断法比100分题解算法更优(当然用字典优化100分题解后显然比排序后的截距判断法更优)
这个代码我写了300多行(请原谅我这个菜鸟),本人设计了十几组实例输入,结果都没问题,但提交后却是错误,也就是有输入实例输出结果错误,测试了几次有点崩溃。感兴趣的朋友可以测试一下,如果找出错误之处敬请指正。
代码如下:
def ec(x,i,ls):
l0 = 0
le = len(ls) - 1
while le - l0 != 1:
lz = int((l0+le)/2)
if ls[lz][0] == x:
ls[lz].append(i)
return 0
if ls[lz][0] sx and s < sy:
return 1
else:
return 0
else:
if s > sy and s < sx:
return 1
else:
return 0
def xl(x,ls,l0=0,le = float("inf")):
if le == float("inf"):
le = len(ls) - 1
while le - l0 != 1:
lz = int((l0 + le) / 2)
if ls[lz][0] == x:
return lz-1
if ls[lz][0] < x:
l0 = lz
else:
le = lz
return l0
def sta(d):
zl = xl(d[0]-d[1],zx)
fl = xl(d[0]+d[1],fx)
# print(zl,fl)
pd(zl,fl,d)
#def reflect(p,d)
def pd(zl,fl,d):
print(d,zl,fl)
lenzx = len(zx)-1
lenfx = len(fx)-1
if d[2] == 0:
loss = float("inf")
k = 1
a = 1.0
zl0,fl0 = zl,fl
zl, fl = zl + 1, fl + 1
if zx[zl][0] == d[0] - d[1]:
zl += 1
if fx[fl][0] == d[0] + d[1]:
fl += 1
# print(zl,fl)
while zl != lenzx:
for xh in zx[zl][1:]:
i = fsm[xh]
if bdx(d[1], i[1], i[3]) == 0:
continue
x = zx[zl][0] - d[0] + d[1]
if x loss:
d[4] = d[4] - loss
d[2] = 2 - k
d[0] = d[0] + loss
if k == 1:
fl = xl(d[0]+d[1],fx,fl0,fl)
pd(zl-1,fl,d)
if k == -1:
zl = xl(d[0]-d[1],zx,zl0,zl)
pd(zl,fl-1,d)
return 0
if d[4] == loss:
d[0] = d[0] + loss
return 0
elif d[2] == 1:
loss = float("inf")
k = 1
a = 1.0
zl0, fl0 = zl, fl
fl = fl + 1
if fx[fl][0] == d[0] + d[1]:
fl += 1
while zl != 0:
for xh in zx[zl][1:]:
i = fsm[xh]
if bdx(d[0], i[0], i[2]) == 0:
continue
x = -zx[zl][0] + d[0] - d[1]
loss = x
k = 1
a = i[4]
break
if a != 1:
break
zl -= 1
while fl != lenfx:
for xh in fx[fl][1:]:
i = fsm[xh]
if bdx(d[0], i[0], i[2]) == 0:
continue
x = fx[fl][0] - d[0] - d[1]
if x < loss:
loss = x
k = -1
a = i[4]
break
if a != 1:
break
fl += 1
# print(zl0,fl0,zl,fl)
if d[4] < loss or a == 1.0:
d[1] = d[1] + d[4]
return 0
d[3] = d[3] * a
if d[3] < 1:
d[0], d[1], d[3] = 0, 0, 0
return 0
if d[4] > loss:
d[4] = d[4] - loss
d[2] = 1 - k
d[1] = d[1] + loss
if k == 1:
fl = xl(d[0]+d[1], fx, fl0, fl)
pd(zl-1, fl, d)
if k == -1:
zl = xl(d[0]-d[1], zx, zl, zl0+1)
pd(zl, fl - 1, d)
return 0
if d[4] == loss:
d[1] = d[1] + loss
return 0
elif d[2] == 2:
loss = float("inf")
k = 1
a = 1.0
zl0, fl0 = zl, fl
while zl != 0:
for xh in zx[zl][1:]:
i = fsm[xh]
if bdx(d[1], i[1], i[3]) == 0:
continue
x = -zx[zl][0] + d[0] - d[1]
if x < loss:
loss = x
k = 1
a = i[4]
break
if a != 1:
break
zl -= 1
while fl != 0:
for xh in fx[fl][1:]:
i = fsm[xh]
if bdx(d[1], i[1], i[3]) == 0:
continue
x = -fx[fl][0] + d[0] + d[1]
if x < loss:
loss = x
k = -1
a = i[4]
break
if a != 1:
break
fl -= 1
if d[4] < loss or a == 1.0:
d[0] = d[0] - d[4]
return 0
d[3] = d[3] * a
if d[3] < 1:
d[0], d[1], d[3] = 0, 0, 0
return 0
if d[4] > loss:
d[4] = d[4] - loss
d[2] = 2 + k
d[0] = d[0] - loss
if k == 1:
fl = xl(d[0] + d[1], fx, fl, fl0+1)
pd(zl - 1, fl, d)
if k == -1:
zl = xl(d[0] - d[1], zx, zl, zl0+1)
pd(zl, fl - 1, d)
return 0
if d[4] == loss:
d[0] = d[0] - loss
return 0
else:
loss = float("inf")
k = 1
a = 1.0
zl0, fl0 = zl, fl
zl = zl + 1
if zx[zl][0] == d[0] - d[1]:
zl += 1
while zl != lenzx:
for xh in zx[zl][1:]:
i = fsm[xh]
if bdx(d[0], i[0], i[2]) == 0:
continue
x = zx[zl][0] - d[0] + d[1]
loss = x
k = 1
a = i[4]
break
if a != 1:
break
zl += 1
while fl != 0:
for xh in fx[fl][1:]:
i = fsm[xh]
if bdx(d[0], i[0], i[2]) == 0:
continue
x = -fx[fl][0] + d[0] + d[1]
if x < loss:
loss = x
k = -1
a = i[4]
break
if a != 1:
break
fl -= 1
if d[4] < loss or a == 1.0:
d[1] = d[1] - d[4]
return 0
d[3] = d[3] * a
if d[3] < 1:
d[0], d[1], d[3] = 0, 0, 0
return 0
if d[4] > loss:
d[4] = d[4] - loss
d[2] = 1 + k
d[1] = d[1] - loss
if k == 1:
fl = xl(d[0] + d[1], fx, fl, fl0 + 1)
pd(zl - 1, fl, d)
if k == -1:
zl = xl(d[0] - d[1], zx, zl0-1, zl)
pd(zl, fl - 1, d)
return 0
if d[4] == loss:
d[1] = d[1] - loss
return 0
sc = []
fsm = {}
zx = [[-float("inf"),0],[float("inf"),0]]
fx = [[-float("inf"),0],[float("inf"),0]]
m = int(input())
for i in range(m):
L = input().split()
if int(L[0]) == 1:
L = list(map(int, L[1:5])) + [float(L[-1])]
k = (L[1] - L[3]) / (L[0] - L[2])
y0 = L[1] - k * L[0]
x0 = ((-1) * y0) / k
fsm[i+1] = L
if k == 1:
ec(x0,i+1,zx)
else:
ec(x0,i+1,fx)
# print(L)
elif int(L[0]) == 2:
j0 = int(L[1])
j1 = 0
del fsm[j0]
for j in range(1,len(zx)-1):
for s in range(1,len(zx[j])):
if zx[j][s] == j0:
zx[j].pop(s)
j1 = 1
break
if len(zx[j]) == 1:
zx.pop(j)
if j1 == 1:
break
if j1 == 1:
continue
for j in range(1,len(fx)-1):
for s in range(1, len(fx[j])):
if fx[j][s] == j0:
fx[j].pop(s)
j1 = 1
break
if len(fx[j]) == 1:
fx.pop(j)
if j1 == 1:
break
else:
print(zx)
print(fx)
cs = list(map(int, L[1:4])) + [float(L[4]), int(L[5])]
if cs[3] < 1:
sc.append([0, 0, 0])
else:
sta(cs)
sc.append([int(cs[0]), int(cs[1]), int(cs[3])])
print(sc)