【题解】[JOISC 2021 Day1] IOI 熱の感染拡大
大模拟,需要肝。
我们一步步分解。
首先最开始只有第一个个点被感染了,那么我们最先固定第一个人的方向,有 \(4\) 种选择。
对于第一个点初始方向上的点,一定与 \(1\) 号点相向运动,否则它们既不会被感染,也不会感染别人,没有任何作用。我们称这类点为 \(\mathbf{A}\) 类节点
对于第一个点初始方向的相反反向上节点,必定向 \(1\) 号点方向移动。
对于剩下点,每个点的方向有两种选择,第一种是垂直于 \(1\) 号点的初始方向运动,第二种是平行于 \(1\) 号点,并向 \(1\) 号点运动。
但是每种点都有两种选择,状态数仍然太大了。
结论:\(1\) 号点初始值位置 \((a,b)\) ,则当前点一定选择到直线 \(x=a\) 和直线 \(y=b\) 中较长的一个。
手动模拟一下发现向另一个走不可能被感染。
对于两个方向相同的点,只可能垂直于初始方向运动,且可以根据相对位置 $\mathcal{O}(1) $ 判断是否被感染。
对于原本就在直线 \(x=a\) 或直线 \(y=b\) 的直线,只可能向 \(1\) 号点方向运动 。
这样我们就固定了所有点的运动方向。
垂直于初始方向运动的点,我们称为 \(\mathbf{C}\) 类点。平行运动的我们称为 \(\mathbf{D}\) 号点。
那么什么情况下会发生传染?
首先被 \(1\) 号点传染的,一定是在初始方向上的,或在过 \(1\) 点,且斜率为 \(1\) 或 \(-1\) 的直线上。 注意这是必要条件,并不充分。
那么两个点之间发生传染,过两点直线的斜率为 \(1/-1\) ,或过两点直线平行坐标轴。
基于一个显而易见的事实,\(i\) 被感染后才能传染别人,所以我们可以运用类似于最短路算法, \(d_i\) 表示节点 \(i\) 被感染的时间。然后讨论感染别人的三个方向的点。
用堆优化 \(\rm Dijkstra\) 算法即可。
但是直接建图的边数可能是 \(n^2\) 的。一个小优化是直线上出现的同向的点,或垂直同向的点可以直接略过。这样每对相向的点只能只会被更新一次,边数为 \(\mathcal{O}(n)\),总时间复杂度 \(\mathcal{O}(n\log n)\) 。
优化的具体做法是对于每条直线 \(x=k\),\(y=k\) ,\(y=x\) ,\(y=-x\) ,从左向右排序,然后扫一遍,同时记录各个初始方向的第一个点 。