题目:
大概的路线是:从A向上走,然后经过LCA(A,B),再向下走。
Z市交通不发达,所有公交路线覆盖的边竟然一个环也不包含,甚至该市的公交路线有可能会分为几个互不连通的块,这可真是不可思议。有一天,你突然听到一条消息,说你的M个同学被困在了Z市里,他们分别要从他们当前所在的点ai移动到他们想去的点bi.于是你立刻调集资料,了解了Z市的形状和公交路线的分布,现在你要算出每个人到达目的地最少要换多少次车,或者告知那个人不能仅用公交车达到目的地。
分析:
先考虑一个询问。假设我们想要从A走到B,像这样:
大概的路线是:从A向上走,然后经过LCA(A,B),再向下走。
对于从A向上走这一部分,如图:
但如果在某处,“最远的一班车”越过了LCA,那情况就变得不确定了——它的路线有可能继续向上,也有可能折向下,我们先不去考虑它。能够确定的是,如果“最远一班车”达到的最浅节点仍然是LCA的后代,坐这班车一定是最优解。
我们对于A和B都执行“在越过LCA之前走最远”的操作。像这样:
答案是多少呢?
如果有一条线路能从A'到B',那答案就是stepA+stepB(共搭乘了stepA+stepB+1条线路,转车次数就是stepA+stepB次)。
如果没有A'~B'的线路,那要么无解,要么答案就是stepA+stepB+1(从A'走到LCA,再从LCA走到B')。
所以答案分成两部分:①对AB执行“越过LCA之前尽量向上走”。②判断A'是否能直接乘车到B'。
我们来分别讨论这两个模块。
①的想法比较显然,用倍增即可。为此对于每个点,需要记录它向上乘1,2,4,8……次车走到的最远点。2,4,8……的情况可以DP出来,关键在于:如何知道从某点出发,向上乘一次车最远走到哪里?
下面来解决这个问题。设点u向上乘一次车走到的最远点是r[u]。
首先,由于我们只关心向上走,所以对于一条线路(s,t),可以将其拆成两条线(s,g)和(g,t),其中g=LCA(s,t)。
得出所有像这样“直上直下”的路径后,我们进行如下处理:
将所有路径按照较浅点的深度从小到大排序,然后对于每条路径(s,t),从t向上遍历到s,如果某个点的r值为零(初始化的值),就将其设为s,否则就退出。
如图:
假设浅蓝色和红色弧线都代表公交线路,深蓝色直线代表一条“直上直下”的链。
在这个算法中,浅蓝色弧线显然比红色线早处理到,因此在处理红色线时,中间重叠部分的r值已经非零。我们沿着红色线从底向上确定那些顶点的r值,在处理到重叠部分时就会跳出。
在这个算法中,浅蓝色弧线显然比红色线早处理到,因此在处理红色线时,中间重叠部分的r值已经非零。我们沿着红色线从底向上确定那些顶点的r值,在处理到重叠部分时就会跳出。
这样,扫描的复杂度就是O(N),因为每个顶点的r值只会被赋值一次,并额外访问一次。
计算出r值之后就可以用倍增解决问题①。
下面来看问题②:
问题②等价于:是否有一条公交线路(s,t),其起点s在A'的子树内,终点t在B'的子树内(或相反,s在B'子树内而t在A'子树内)。
即:是否有一条公交线路(s,t),满足dfn[A']<=dfn[s]<=last[A']且dfn[B']<=dfn[t]<=last[B'](或相反)。
其中dfn[x]代表x节点在DFS时首次被遍历到的时间,last[x]代表x节点最后一次被遍历到的时间,显然夹在二者之间的那些节点就是x的子树。
把这两个限制改写一下:在x坐标范围dfn[A']~last[A'],y坐标范围dfn[B']~last[B']的矩形中,是否存在某个点(dfn[s],dfn[t])或(dfn[t],dfn[s])。
也就是:给定一堆点和一堆矩形,问每个矩形内包含了多少点。(其实只需要判断有没有)
这是一个经典的问题。建立一棵线段树(其实树状数组就行了),用水平扫描线从上向下扫描,每扫到一个点就在线段树相应位置++,每遇到矩形上/下边界就记录下来当前的值,那么每个矩形内的点数就是下边界的值减去上边界的值。
这就解决了。当然,这道题写起来还是比较烦人的……
代码: