洛谷 P4125 题解
考虑先做第二问,再做第一问.
我们想要构造一种
哪 1 片针叶是可以立刻就移走的?比如说我们想要让这片针叶向下直接移走,那么这片针叶下面就不能有任何的阻碍,一个粗糙的想法是直接选择
但是这样可能会出问题,如下图所示:
如果我们直接向下移动红色的针叶,会被蓝色的针叶所阻碍.
这启发我们,找到一片没有被任何针叶阻碍的针叶,还是以向下移走为例子,我们看,如果只考虑上下之间的障碍,能否一定能够找到一片,没有被任何针叶阻碍的针叶.
首先,如果针叶
因此,如果对于每个针叶
比如说对于我们的第一个样例而言,针叶建点,阻碍关系建边,就是这样一张图:
知道了有向无环图有什么用呢?任意一张有向无环图上,一定存在一个结点,没有任何入度.
反证法,如果所有结点都有入度,反图上所有结点就都有出度,此时我们任意选一个点不断走下去,瞎走一通,走到
这样,我们一定能够找到一片针叶
这启示我们,如果能够把这张有向无环图真正建出来,它的拓扑序的逆序列就是一个合法的移动序列,按照这个移动序列,一片一片向下移动针叶即可.
直接建图需要枚举
注意到,如果针叶
只知道这个性质是没有什么用的,我们并不能刻画出一片针叶
考虑换一种方式刻画阻碍关系,不再思考点对点贡献,而是直接思考,某个给定的
假如不考虑边界情况,所有与这条直线有交点的针叶,两两之间都存在阻碍关系.
建图的时候,只要在相邻两片针叶之间,建一条边即可.
如果我们把这条直线稍稍向右移动一些,你可以认为移动了
如果移动很多呢?即使跨过了很多整点,只要与这条直线有交点的针叶集合没有变化,能建的边就都不会有变化,回归点对点贡献的思考.
对于两片针叶
也就是说,对于某个集合的针叶来说,对于所有能够把它们全截下来的,与
用一条与
假设我们已经处理完了
- 可能会新来一片针叶
- 需要确定在这片针叶上面的针叶中最靠下的,也就是前驱;再确定在这片针叶上面的针叶中最靠下的,也就是后继,那么新来的针叶阻碍了前驱,后继阻碍了新来的针叶.
- 用一个
\texttt{STL Set} ,可以维护所有,与目前扫描线有交点的针叶的编号,新来一片针叶,先在\texttt{Set} 中查询前驱、后继,之后在\texttt{Set} 中加入这片针叶即可.
- 可能会有一片针叶消失了
- 并不需要做什么事情,只要在
\texttt{Set} 中删除这片针叶即可.
- 并不需要做什么事情,只要在
如果这样处理,可能会面临一些小问题:
- 如何重定义
\texttt{Set} 的小于号,使得我们可以在不断改变的x 坐标下比较两条线段的y 坐标?- 利用序不改变的性质,我们可以 ”欺骗“
\texttt{STL} :虽然我们传进去的小于号不是严格全序,但是在\texttt{STL} 的运作周期内,永远都不会发现这一点. - 将每条直线用斜截式
y=kx+b 表示,那么k 和b 是直线的参数,x 是递增的,y 是需要比较的关键字,之后我们重定义小于号,从某个全局参数处读取此时的x 进行比较. - 这就是说,在扫描线的过程中,每扫到一个有变化的
x 坐标,就微调一下小于号的规则. - 因为前面提到的序不变的性质,在
\texttt{STL} 的运作周期内,不可能发现该小于号不是严格全序.
- 利用序不改变的性质,我们可以 ”欺骗“
- 如果同一条直线上,又有插入又有删除,该怎么处理?
- 不难发现,此时要插入的直线和要删除的直线之间,实际上是不能连边的.
- 对同一个
x 坐标对应的截线,只要一律先处理删除,再处理插入即可.
- 如此,对于每片针叶,我们会在刚扫到这片针叶的时候,让这片针叶连入、连出各不超过 1 条边,这样我们就建出来了,实际阻碍关系图的一张等效图,图上有
n 个结点和不超过2n 条有向边.
以上是考虑从上到下的阻碍关系建出来的图,我们对这张图作拓扑排序,令针叶
对于第一个样例而言,一组可能的
按照拓扑序,将所有针叶向上移动,或者向右移动,可以正确回答所有数据点的第二问,时间复杂度为
这里要特别注意,实际考试如果遇到这样分步给分的题目,对不想回答的小问,一定要注意什么样的输出是不给分但是可以被接受的;在这道题中,如果第一问随意地输出了
在验证了
考虑如何判定一步给定的移动操作是不是合法的,这次以向上直接移动为例子.
将一片
注意到我们直接在刚才建出来的图上找是不可行的,有这样的一些原因:
- 可能有些针叶
x 能对u 造成阻碍,但是我们把x 移走了; - 如果要处理移走了的针叶,仅仅靠一张等效图是不够的,而原图又没有办法建出来.
这里最大的问题,是我们要试图删除一片针叶,这会破坏这张等效图的等效性.
正难则反,删除操作不可行的时候,考虑逆转时间轴,时间倒流,考虑插入操作.
也就是说,我们从已经被移空了的平面开始,按照操作顺序的倒序,一片一片加入针叶,加入的过程就是从无穷远处移动回原来所在的地方,看哪些针叶会在这个加入的过程中被阻碍.
注意到如果这样做,是不可以剪枝的,必须考虑每一片针叶,因为有可能后来的针叶被阻碍了,前面的针叶也被阻碍,这也就意味着无论一片针叶有没有被阻碍,我们都要在平面上将这片针叶安排回原来的位置,因为这片针叶还有可能去阻碍别人,如果考虑逆转时间轴的实际意义,很容易说明这一点.
既然单从图论的角度难以直接解决问题,我们还是回归移动针叶被阻碍这件事的实际意义.
一片针叶
考虑这些针叶,如何判断这些针叶到底会不会对
注意到这里考虑到的所有针叶对
既然一定存在阻碍关系,那其实就好办了,还是回归那张等效图.
这时解法基本上已经呼之欲出了,我们不是求出了等效图拓扑序
可能会产生一个疑问:如果直接当作
所以判断其实很简单:考虑所有坐标范围与
需要用数据结构快速维护这个过程,不难想到线段树,具体过程如下:
-
将所有移动操作读入进来离线,逆转时间轴,逆序考虑所有的操作;
- 当然在这之前,要先离散化所有的坐标,注意离散化是在这一步才进行,而不是在一开始的时候就进行离散化,否则因为斜率等问题会产生错误.
-
建立两棵线段树
T_x 和T_y ; -
设这一步移动操作移动的针叶编号是
u ,u 的x 坐标范围是[l_x,r_x] ,y 坐标范围是[l_y,r_y] :- 设布尔标记
\texttt{Flag} 为真; - 如果是操作
\texttt{0} ,也就是向左移动: - 在
T_y 中查询[l_y,r_y-1] 的区间最大值,如果该值大于\beta_u ,就令\texttt{Flag} 为假; - 如果是操作
\texttt{1} ,也就是向上移动: - 在
T_x 中查询[l_x,r_x-1] 的区间最小值,如果该值小于\alpha_u ,就令\texttt{Flag} 为假; - 如果是操作
\texttt{2} ,也就是向右移动: - 在
T_y 中查询[l_y,r_y-1] 的区间最小值,如果该值小于\beta_u ,就令\texttt{Flag} 为假; - 如果是操作
\texttt{3} ,也就是向下移动: - 在
T_x 中查询[l_x,r_x-1] 的区间最大值,如果该值大于\alpha_u ,就令\texttt{Flag} 为假; - 在
T_x 中以\alpha_u 的值更新区间[l_x,r_x-1] ; - 在
T_y 中以\beta_u 的值更新区间[l_y,r_y-1] ; - 若
\texttt{Flag} 为假,记录目前的操作步骤i 是不合法的;
- 设布尔标记
最终最早的不合法步骤即为第一问的答案,可以正确回答所有数据点的第一问,时间复杂度为
常数主要在线段树上,如果写 zkw 线段树可以降低常数,但是考虑到 3 秒的时间限制,没有必要.
肝了 3.5h,接近 300 行、7.7 个 KB 的代码不得贴一下 qwq.