[GXOI/GZOI2019] 旅行者
算法
神经算法
对于 \(\rm{Subtask} \text{ } 1\) , 直接跑 \(n\) 遍 \(\rm{dijkstra}\) 就可以, 这是 \(O(T n ^ 2 \log n)\) 的
对于 \(\rm{Subtask} \text{ } 1\) 的优化:
显然的, 每次 \(\rm{dijkstra}\) 只需要跑到离自己最近的感兴趣的点即可, 因为后面的不可能更优
时间复杂度玄学, 但是会被卡
但是思路令人震撼
\(O(T n \log^2 n)\) 做法
容易的, 建立超级源点向其中一半的点 (以下记为 \(A\) 集合) 连接一条边权为 \(0\) 的边, 再从另一半 (以下记为 \(B\) 集合) 向超级汇点连接一条边权为 \(0\) 的边, 跑超级源点到超级汇点的最短路
但是这样不一定能解决双点属于同一集合问题
容易想到随机化选点之后, 多跑几次, 每次成功的几率为 \(\frac{1}{4}\)
随机化 \(20\) 次后, 成功几率达到了 \(99.7\%\) , 包过的啊
考虑正确性做法,
做法 1
我们可以根据关键点编号在二进制中第 \(i\) 位上的数字来对这 \(k\) 个关键点进行分组, 为 \(0\) 的分在一组, 为 \(1\) 的分在另一组, 然后按照上面的方式建图求最短路
假设最优解中的起点为 \(u\) , 终点为 \(v\) , 那么它们编号在二进制中至少有一位不同, 所以它们必定会在某一次分组中被分在了不同的组, 从而求解出了答案
时间复杂度 \(O(T n \log^2 n)\)
做法 2
观察到我们需要一种办法, 来 \(O(\log n)\) 的枚举所有可能的点集分裂方式
考虑任意两个叶子在树上都有唯一的 \(\rm{LCA}\) , 考虑每一次连接一颗完全二叉树的点的左右儿子
也是 \(O(\log n)\) 的枚举方式
具体见 Luogu 题解
两种做法都高效的达成了分割集合的作用, 是巧妙的, 都是将一个完全图分成了 \(\log\) 级别个数的二分图且任意两点都在一种分割里面在不同集合
\(O(T n \log n)\) 做法
令人惊叹的做法!
对 \(\rm{dijkstra}\) 进行了巧妙优化, 使其可以处理一种 类多源最短路 的问题
考虑每条边 \((u, v, w)\) 的贡献, 我们可以处理出城市中感兴趣的点距离 \(u\) 最近的距离 \(dis_1\) , \(v\) 距离城市中感兴趣的点的最短距离 \(v\) , 那么这条边的权值就是 \(dis_1 + dis_2 + w\)
然后找到最短的一条就行了, 复杂度 \(O(n \log n + m)\)
本质上, 是通过每一条边松弛最近两点的最短距离, 使得 \(\rm{dijkstra}\) 在面对多源最短路时避免重复计算 ( 即已经跑过的最短路不用跑 )
但是这样做依然错误, 观察原因, 发现如果距离 \(u\) 最近的 城市中感兴趣的点 和 距离 \(v\) 最近的 城市中感兴趣的点相同 (即出现环) , 显然是不符题意的, 需要舍去
代码
这里只提供 \(O(T n \log n)\) 做法的代码, 关于一些实现的小细节 :
- 如何解决有向图
显然可以建反边跑两次 \(\rm{dijkstra}\)
- 如何确定最近点
考虑使用染色法, 将每个感兴趣的点标上颜色, 在松弛时染色, 即可知道距离这个点最近的感兴趣的点
实在是太困了, 代码先不打了, 有时间再说
总结
这一类型的题应该想到超级源点和超级汇点, 后面无论是分割方法还是随机化算法都可以推
对算法的了解需要更深入, 本题中 \(\rm{dijkstra}\) 和染色法的应用都十分经典
对于本题中 \(\rm{dijkstra}\) 的优化, 其实是松弛算法的另外一种体现