首页 > 其他分享 >长链剖分优化

长链剖分优化

时间:2023-03-13 16:23:23浏览次数:42  
标签:长链 剖分 ... dep 复杂度 now 优化 dp

概述

  • 长链剖分通过对 DP 状态的复用,有效地降低某些状态具备显著继承性的 tree dp(多为与当前子树深度有关的 dp 状态)的转移复杂度。

  • 也可以说这是把本质不同的 dp 状态压到了一起。

实现原理

  • 我们举最典型的能被长剖优化的 dp。

    • 状态:\(dp_{i,dep}\) 表示考虑了以 \(i\) 为根的子树中,以 \(i\) 为参考时深度 \(\leqslant\) 或 \(=dep\) 的点的某个价值/...。这里为了方便写转移式我们取 \(=\)。

    • 转移:\(dp_{i,dep}=(\sum/\prod/\max/\min/...\ dp_{j,dep-1})+/-/\times/...\ x\),其中 \(j\in sons_i\),\(x\) 为节点 \(i\) 本身的贡献之类的。

  • 注意到,这个式子很像是把整个 \(dp_j\) “向右平移一格”了之后“摞”到一起的结果。

  • 但是数组平移很烦诶。我们能不能 \(O(1)\) 地平移?我说,可以!

  • 首先我们对树进行一个长剖,然后按 DFS 序来做这个 DP。

  • 和我共长链的一个儿子:可以用巧妙的方式来继承过来。仔细想一想,假设我的 \(dp_i\) 的 \(dep\) 从 \(1\) 开始,那我如果勒令我的长儿子的 \(dep\) 把 \(2\) 作为开头指针,那我岂不白嫖?

  • 不共长链的虚边儿子:暴力转移。注意,每次走虚边,都需要开一个“截然不同”的开头指针,这个开头指针是在空内存段上的,即,是在最后一个结尾指针的后边,否则各个重链的答案会混到一起。

  • OK,那我们来考虑一下复杂度。显然 \(i\) 本身的修改是 \(O(1)\) 的。

  • 每个长链,只有在对应内存段被填满之后,才需要暴力继承。显然这一暴力的复杂度就是 \(O(len)\),\(len\) 为长链长度,毕竟长度就是 \(dep_{max}\)。

  • 这一次继承之后,就可以认为这个长链被合并到了那个更大的长链里;以后有什么贡献都是那个大长链的。

  • 综上所述,复杂度为 \(O(n)\)。妙绝!

例题

进击的 95

  • 题意:

    • \(n\) 个点的无边权树,\(m\) 组询问,每组询问问的是被包含于 \(S(x,d)\) 的集合构成的集族的元素个数。

    • 其中 \(S(x,d)\) 表示的是以 \(x\) 为根的子树内到 \(x\) 距离不超过 \(d\) 的点的集合。

  • 数据范围:\(n,m\leqslant 10^6,5s\)。

  • 容易看出这个东西具有递推性,如果定义 \(dp_{i,d}\) 为 \(S(i,d)\) 对应的答案的话,显然有如下转移式:\(dp_{i,d}=min(mxdep_i,d)+1+\sum\limits_{j\in sons_i} dp_{j,d-1}\)。

  • 其中 \(mxdep_i\) 指的是孤立考虑当前子树时,最深的点的深度(认为 \(dep_i=0\)),也可以说是当前子树内到 \(i\) 最远的点到 \(i\) 的距离。

  • 显然后面的和式可以用长链剖优化。

  • 但是看一下前面这个东西,仔细观察发现它具有典型的一次函数性质(如果我们把 \(dep_i\) 定义为 \(1\) 的话,只要把 \(min\) 的第二项换成 \(d+1\),这就是个严格正比例函数)。问题是,怎么 \(O(1)\) 地做这个?

  • 感觉可做(参考奇怪的函数),但关键是长链剖本身的空间分配还导致我们是依次给 \(e-1\sim e,e-2\sim e,\dots,s\sim e\) 加一次函数...暴力合并倒是好做,直接对函数求值然后把函数删了就好。

  • 可是 \(1e6\),而且 \(5s\)。

  • 那谁写那个麻烦的东西啊。起开,上线段树!

  • 用线段树维护差分意义下的 \(dp\) 数组。于是加一次函数就是区间修改,取值就是求前缀和。\(O(m\log n)\)。

  • 其实这里可以偷个懒:一般的差分要在作用范围结束之后把影响减掉,可是我们有 \(s,e\),而且每次加一定是一直加到 \(e\),所以可以不去减那个影响,然后把前缀和换成从 \(s\) 开始的前缀和。

  • 另外,我们这里可以在暴力的时候把对应 \(dp\) 取出来并改为 \(0\),然后甚至可以空间复用。

  • 就是常数很大,极限数据要跑 \(2s\) 出头。不过这个写法很好写,很漂亮。

  • 部分代码展示:

int s[maxn],e[maxn],thee;
//每个点在 dp 中的起/止指针;dp 的内存已经被用到了哪里。请注意朴素 dp 的下标从 0 开始。
il void dfs2(int now){
	if(longs[now]){
		s[longs[now]]=s[now]+1,e[longs[now]]=e[now];
		dfs2(longs[now]);
	}
	for(ri int i=hd[now];i;i=nxt[i])
		if(to[i]!=longs[now]){
			s[to[i]]=thee+1,e[to[i]]=thee+mxdep[to[i]]-dep[to[i]]+1;
			thee+=mxdep[to[i]]-dep[to[i]]+1; 
			dfs2(to[i]);
			for(ri int j=s[to[i]],del=1;j<=e[to[i]];++j,++del)//del 是相对当前点来说的。 
				ins(1,1,n,s[now]+del,get(1,1,n,j));
			thee=s[to[i]]-1;
		}
	rev(1,1,n,s[now],e[now],1);
	for(ri query qn:q[now]) ans[qn.id]=qsum(1,1,n,s[now],min(e[now],s[now]+qn.v));
	return;
}

il void work(){
	s[1]=thee+1,e[1]=thee+mxdep[1]-dep[1]+1;
	thee+=mxdep[1]-dep[1]+1,dfs2(1);
	For(i,1,m) wth(ans[i]);
	return;
}
  • \(longs\) 长儿子。\(ins\) 和 \(rev\) 略,\(get\) 就是单点取值,然后改成 \(0\) 的操作。

  • 这里把询问挂在点上处理了,毕竟长剖优化本身就是会破坏信息的,只能离线。

  • 那个空间复用我感觉可以被卡但不完全能卡...菊花图下相当于空间 \(O(1)\),一般图的情况,我感觉不会太好,但应该有兜底。不过不重要,随手写一下而已。

P1600 [NOIP2016 提高组] 天天爱跑步

  • 题意略。

  • 显然我们可以把每个人跑的路程强行拆成上行/下行两段,可以分别长剖优化。

  • 对于上行,其开始时间一定是 \(0\),故相当于一个朴素长剖。

  • 对于下行,其可能是拐弯拐过来的,此时复杂度不太对:

    • dp 数组并不是只会用到 \(dep\) 以内的地方。此时需要用 u_map 优化。

    • 之所以这么做复杂度是对的,是因为开出的点有限...假了。

    • 好吧。看来我是假做法过的题。对于下行,我们需要重剖,然后在重剖的链上用长剖优化。复杂度证明为每个插入操作最多被合并 \(\log\) 次。

  • 总复杂度 \(O((n+m)\log n)\),求 lca+合并。

  • 可以做到更优的复杂度:使用全局偏移+差分。

  • 回忆我们在 \(\text{P2414 [NOI2011] 阿狸的打字机}\) 中的欧拉环游序做法:

    • 对于深度为 \(k\) 的点,\(cnt_{t+k}\) 为第 \(t\) 秒时经过它的人数(具体的看上下行情况来决定)。

    • 那么,只要我们这样赋值,并且这样取值,就对...

    • 然而不对。考虑一种情况,\(son_1\) 对 \(cnt\) 的修改,可能会被 \(son_2\) 认为是自己的答案。

    • 在阿狸的打字机中,我们的办法是 dfs 退出时即退栈,删去影响。然而这里我们的问题是子树性的,只需要一条链在生效的美好兴致不存在咯!

    • 解决办法:差分。在进入子树的时候记录桶的情况,退出时的 \(cnt-rec\) 就是答案。它们是增量,而实质就是子树内的量。

    • 时空双炸?啊...哈哈。我们只关心 \(w_{now}\) 对应点那里的啊,记录一个就行了。

  • 合并复杂度没了,但是...我不会 \(O(n+Q)\) 的求 lca(需要树分块),所以...还是 \(O(n\log n)\)。

标签:长链,剖分,...,dep,复杂度,now,优化,dp
From: https://www.cnblogs.com/weixin2024/p/17211827.html

相关文章

  • 无法获取本地变量或参数的值,因为它在此指令指针中不可用,可能是因为它已经被优化掉了
    问题:调试时,变量的值无法显示,打印变量值提示"无法获取本地变量或参数的值,因为它在此指令指针中不可用,可能是因为它已经被优化掉了"。解决办法:取消"优化编码"勾选框勾选状......
  • 15.mysql优化建议一
    硬件优化:1.为提高数据库的IOPS性能,可以使用ssd或者pcie-ssd告诉磁盘设备2.当数据库系统tps过高或者业务量较高时,一定要配置阵列卡,阵列级别选择REID1+0,而不要选择其他格式......
  • Mysql优化
    1,表设计一定要优化,冗余数据最少,少用连接查询。如果在实际应用中,使用了极其复杂的连接,子查询,则数据表的设计得要重新考虑了。2,尽量用char而不是varchar,因为固定长度得str......
  • 单页应用优化--权限
    前段时间,撰写过“​​单页应用优化–懒加载​​”的问题,这篇我们描述一下单页应用的另外一个问题权限。提起权限,一般会涉及如下几种情况:应用使用权【登录】页面级别权限【......
  • mysql优化策略
    1看一下能不能加缓存解决,是不是周期性的卡顿,是否需要调整缓存失效策略2开启慢查询日志,看看是sql执行的时间长还是sql等待的时间长查看慢查询日志setgloballong_que......
  • 非线性优化问题基本形式概述
    非线性优化问题以及在视觉SLAM中的应用1.0最小二乘基础概念定义\(\quad\)找到一个n维的变量\(\mathbf{x}^{*}\in\mathbb{R}^{n}\),使得损失函数\(F(\ma......
  • vue常见的优化手段
    前提:永远不要过早地优化,仅在影响运行、卡的不行的时候才优化[参考]代价:代码会变得难以阅读,开发难度增大使用key对于通过循环生成的列表,应给每个列表项一个稳定且唯一......
  • MySQL模糊查询like优化方案
    索引失效的解决方案在MySQL中,模糊查询肯定要使用LIKE关键字,然后再加%,是代表前模糊还是后模糊。数据量小的情况下,不容易看出查询的效率,但是数据量达到百万级,千万级甚......
  • 系统性能优化十大绝招
    上篇 引言:取与舍 软件设计开发某种意义上是“取”与“舍”的艺术。 关于性能方面,就像建筑设计成抗震9度需要额外的成本一样,高性能软件系统也意味......
  • 表数据量大优化方案设计
    场景:有一个订单功能,里面的主表有几千万数据量,加上关联表,数据量达到上亿。我们尝试了优化表结构、业务代码、索引、SQL语句等办法来提高响应速度,但查询速度还是很慢。一......