[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}\) 的优化, 其实是松弛算法的另外一种体现

posted @ 2024-11-14 21:05  Yorg  阅读(1)  评论(0编辑  收藏  举报