CF576D - Flights for Regular Customers 题解

开始挖 tzc 遗产(?

遗址

这好像是上个赛季的一个 jxd 作业。


首先直接 BFS 肯定是不行的,因为你会一边走边会一边增加,那就要加一维步数,那肯定吃不消的。

显然,边集只有 \(\mathrm O(m)\) 种,而且对应着若干个连续的时间段。我们不妨枚举最优答案的最后一步是在哪个时间段里面。然后对于每个时间段,它的边集显然是固定的了,那么就可以直接 BFS 了。

那么现在瓶颈在于,该时间段左端点的时候哪些点是可以从 \(1\) 出发走到的,这个怎么求,求出来就 BFS 就行了。

这个就是比较套路的东西了。某些萌新可能迷惑邻接矩阵到底有什么用,它明明能被邻接表代替。它终究是一个矩阵,满足这样一个性质:两个邻接矩阵相乘,所得积的 \((i,j)\) 表示若先走前者代表的边集,后走后者代表的边集,那么 \(i\to j\) 的路径数。这也太容易理解了,就是矩乘的定义。然后如果用 bool 型存的话就表示可达性,它相当于是一个广义矩乘,也是满足性质的。

那么每个时间段的初始点集就是之前的若干个邻接矩阵相乘看第一行就可以了。这个可以从前往后扫的过程中一路乘,对于每个时间段乘快速幂。那复杂度就是 \(\mathrm O\!\left(n^3m\log\right)\),过不去。然后是另一个 trick:矩阵里都是 bool,于是可以搞出乘法中前者的行和后者的列的 bitset,然后查询的话复杂度就除以了一个 \(w\)

code


upd. 2020.12.16:

之前就看到题解区里面有复杂度更优的算法,一直鸽到现在。

复杂度的瓶颈显然在于「那么现在瓶颈在于,该时间段左端点的时候哪些点是可以从 \(1\) 出发走到的,这个怎么求」。我们希望搞出不需要 bitset 优化的方法。注意到我们虽然维护了邻接矩阵的积,但是最终我们想要的答案确是 \([1\ 0\ 0\ \cdots\ 0]\) 乘以这个矩阵。那么我们很套路的联想到 NOI D1T1,通过向量乘矩阵是 \(\mathrm O\!\left(n^2\right)\) 的这个性质来优化。

套路的方法是考虑二进制光速幂,预处理出矩阵的 \(2\) 的整次幂的幂。那么现在如果任意时刻,这个维护的东西都是正确的话,那么复杂度显然是 \(\mathrm O\!\left(n^2m\log\right)\) 了。那么如何在加边的过程中实时更新这个维护的东西呢?先从最底层考虑,加一条边 \((i,j)\)\(0\) 次方的影响显然就是 \((i,j)\) 变成 true。然后如果成功改变它了,那么对 \(1\) 次方影响是啥呢?回想矩阵乘法公式 \(C_{i,j}=\bigvee\limits_{k}A_{i,k}\land B_{k,j}\),那么分为两种情况,\((i,j)\) 充当 \(\bigvee\) 后面式子的左项还是右项。前者通过与 \((j,k)\) 配合影响 \(1\) 次方的 \((i,k)\),后者通过与 \((k,i)\) 的配合影响 \((k,j)\)。然后考虑 \(1\) 次方如何影响 \(2\) 次方,其实就是对于每次成功修改像 \(0\to 1\) 那样套娃就可以了,可以递归写。

如何证明复杂度是对的呢。感觉上觉得很对,也很好证。它的复杂度由两部分构成:每次循环寻找成功修改对象的时间,和成功修改次数。后者来说的话,显然满打满算也是总的是平方对数了,那么前者就是每次成功修改(注意只有成功修改才会尝试影响下一个次方,否则 wyy)都花 \(\mathrm O(n)\),总的就是三方对数了。

是不是「思维共套路一色」?

code

posted @ 2020-12-13 14:30  ycx060617  阅读(112)  评论(1编辑  收藏  举报