记一类分治思想
这种分治思想大概是我在一年前看到的,但当时只看到了一道能用的题,就并没有当回事,最近又见到了可以用的题目,来写个随笔记一下。
不太清楚这个叫啥分治,要是有人在看而且知道可以跟我讲一下。
时间线段树(线段树分治)与这一思想较为类似,可以去看我的 时间线段树(线段树分治)学习笔记 。
问题一:给定一个 点 边有向图,对于所有 ,求 到 且不经过 的最短路长度之和。
这是我最早见到的这个思想的题目,虽然没找到题在哪里。
容易发现暴力 Floyd 一遍是 的,能不能想一个更快的算法呢?是有的。
考虑 Floyd 算法的最一般形式的由来,设 表示 到 只能经过 集合中的点的最短路,有转移方程:
然后按顺序将节点加入 之后滚掉 这一维就是 Floyd 算法。
注意到节点加入 的顺序其实是不重要的,发现在刚刚的 流程中没有充分利用这一点,进行了很多重复计算。考虑这样一种分治算法:
FLOYD(int[][]& dp, int l, int r) # 在 dp 数组的基础上,再添加 [l, r] 的点到点集 S 中
for k in [l, r] do
for i in [1, n] do
for j in [1, n] do
dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j])
DIVIDE_AND_CONQUER(int[][] dp, int l, int r) # 计算不加入 [l, r] 范围的点的全源最短路
result = 0
if l == r do
for u in [1, n] do
for v in [1, n] do
result += dp[u][v]
else do
mid = (l + r) / 2
dp_new = dp
FLOYD(dp_new, mid + 1, r)
result += DIVIDE_AND_CONQUER(dp_new, l, mid)
dp_new = dp
FLOYD(dp_new, l, mid)
result += DIVIDE_AND_CONQUER(dp_new, mid + 1, r)
return result
这种算法的正确性显然,时间复杂度可以写作 ,用主定理可知为 。
观察以上过程,可以简单地总结一下这类分治算法的流程:
DIVIDE_AND_CONQUER(l, r)
:要求解 范围的答案。- 如果 ,对这个点求解即可。
- 否则取中点 进行分治:
- 添加右半边对左半边的贡献,递归左半边统计答案。
- 将右半边的贡献擦除,添加左半边对右半边的贡献,递归右半边统计答案。
- 如果需要,将两部分答案合并。
总结:这类分治算法通过求解多个点的答案时共用信息的方式,大大减少了计算量,通常可以把一个暴力计算的 降低到 。
问题二(CF1681F):有一棵 个点的树,每条边有颜色,对于每条路径 ,求路径上恰好出现一次的颜色数量的和。
本题有一个 做法,是类似虚树的思想,对每个颜色分开统计答案,但这不在我们讨论的范畴。
我是从 tourist 大神的 代码 里再次看到的这个思想。
不难发现,对于颜色 的答案即为把所有颜色 的边删掉后,每条 边连接的两个连通块大小之积的和。
接着利用上面的思想,DIVIDE_AND_CONQUER(l, r)
即为要求解 范围的答案。
使用可撤销并查集维护连通块,对于分治中心 ,我们把右半边的边连接的连通块合并,然后统计左半边答案,之后执行撤销操作,把左半边的边连接的连通块合并,然后统计右半边答案。对于单点的求解,枚举颜色 的边,求两个连通块大小之积即可。
时间复杂度为 。
问题三(P4141):背包,所有少一个物品的情况的最大价值之和。
有 做法,不过由上面的分治,可以有 做法。
其他应用:“线段树分治”(时间轴线段树)的思想比较类似,只是统计贡献的方式略有不同。
问题四(ABC279E)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现