1 条题解
-
0
题解:动态维护树上异或值计数
问题转化
给定一棵 个节点的树,每个节点有权值 (, 是 的幂且 )。
定义一棵连通子树的价值为子树中所有节点权值的异或和。
支持两种操作:- 修改单个节点的权值
- 查询价值为 的非空连通子树个数
答案对 取模。
关键性质
是 的幂,,所以权值范围很小,可以用异或卷积处理。
树形 DP
设 表示以 为根的子树中,包含 且价值为 的连通子树个数。
设 表示以 为根的子树中,不要求包含 (即可以包含 的子树的某个连通块)且价值为 的连通子树个数。初始化:(只有 自己), 初始全 。
转移:考虑 的一个子节点 ,已经合并了前若干个子树的 ,现在加入子树 。
新的 由两部分组成:
- 不含 子树:
- 包含 子树: 和 的异或卷积(因为 必须选, 子树也必须包含 )
具体来说: 令 (异或卷积),则 $f_u'[x] = f_u[x] + \sum_{a \oplus b = x} f_u[a] \times f_v[b]$。
但注意: 和 卷积表示 所在的连通块和 所在的连通块合并(通过边 连接),形成新的连通块,价值为两者异或。
因此:
这里 是异或卷积。
对于 :
- 不含 子树:
- 包含 子树:
- 和 卷积后得到的新连通块也贡献给 (因为这些连通块在 的子树内) 但 实际上表示整个已合并子树中的答案,所以:
最终对于整棵树(任选根,比如 ),所有价值为 的连通子树个数就是 。
异或卷积优化
由于 ,可以直接 卷积,但 ,总复杂度 过大。
利用 Fast Walsh-Hadamard Transform (FWHT) 优化异或卷积到 。
对于两个数组 长度 ( 是 的幂),FWHT 可以在 时间内计算 。
步骤:
- 对 做 FWHT 变换得
- 对 做逆 FWHT 得
由于模数 很小,直接整数运算即可。
动态 DP
问题在于有修改操作,需要动态维护 。
使用 动态 DP 技巧,将树剖分成重链,对每条重链用线段树维护矩阵乘积。
定义状态向量 ,其中 是长度为 的数组, 是常数 。
转移可以写成矩阵形式: 设 有轻儿子 ,重儿子 。 令 仅考虑轻儿子的贡献(即 初始为 ,然后与所有轻儿子的 卷积)。 类似定义 仅考虑轻儿子的贡献。
则 可以由 通过一个转移矩阵得到:
$$\begin{bmatrix} f_u \\ g_u \\ 1 \end{bmatrix} = \begin{bmatrix} LF_u \otimes f_{son} & 0 & LF_u \\ LG_u + g_{son} + (LF_u \otimes f_{son}) & 1 & LG_u \\ 0 & 0 & 1 \end{bmatrix} \times \begin{bmatrix} f_{son} \\ g_{son} \\ 1 \end{bmatrix} $$实际上更精确的: (注意这里 是已经包含 和轻儿子的基础数组)
因此转移矩阵 作用在 上得到 。
由于 是数组,矩阵的每个元素是一个线性变换(即对数组的卷积或加法)。
简化:标量化
由于 ,我们可以将数组视为向量,转移矩阵就是 维的矩阵,但矩阵乘法复杂度 不可接受。
更好的方法:直接在线段树节点维护 和 数组,合并时用 FWHT 卷积。
重链剖分 + 线段树
对每个节点 ,预处理:
- : 与所有轻儿子卷积后的 数组(初始为 )
- : 的所有轻儿子的 之和
对于重链,从叶子到根用线段树维护合并: 线段树节点 存储:
- :该区间节点组成的连通块(包含 top)的 数组
- :该区间内的总答案
合并两个相邻区间 和 : 设左区间对应节点 (深度较小),右区间对应其重儿子链。 合并方式与 DP 转移相同:
这样,线段树根节点存储的就是整条重链的答案。
对于整棵树,从根开始,依次合并每条重链。
修改操作
修改节点 的权值:
- 更新 的 (因为 变了)
- 更新 所在重链的线段树
- 由于 的 改变,会影响其父节点的 (如果 是父节点的轻儿子)
- 不断向上跳重链,更新受影响链的线段树
每次更新复杂度 。
查询操作
查询价值为 的连通子树个数: 整棵树的答案存储在根所在重链的线段树根节点的 数组中,直接输出 。
初始化
先 DFS 预处理每个节点的 ,然后对每条重链建线段树。
算法步骤
- 读入 边,建树。
- 树链剖分,得到重儿子、top 等。
- DFS 计算每个节点的 :
- 初始为只有 的数组(即 )
- 初始全
- 对每个轻儿子 : (这里 是 为根的子树答案,需要递归计算)
- 计算 (暂时用于初始化)
- 对每条重链建线段树,每个叶子节点对应一个节点 ,存储 。 线段树合并时使用 FWHT 卷积。
- 处理操作:
- Change x y: a. 更新 b. 重新计算 的 (初始化为 ,然后与所有轻儿子的 卷积) c. 更新 所在重链的线段树 d. 设 为 的父节点,若 是 的轻儿子,则更新 的 (需要除去旧的 贡献,加入新的 贡献) 由于模数小,无法直接除法,可以用“减-加”方式:$lf_p = lf_p - (lf_p \otimes old\_f_x) + (lf_p \otimes new\_f_x)$ 但注意这是卷积操作,不能简单加减。实际需要重新计算 的所有轻儿子的贡献。 简单做法:记录 的所有轻儿子列表,重新卷积。 e. 重复向上更新直到根。
- Query k: 输出
- 所有操作结果对 取模。
复杂度
- 预处理:
- 修改: 轻儿子数,轻儿子数均摊
- 查询:
由于 ,可过。
- 1
信息
- ID
- 6079
- 时间
- 3000ms
- 内存
- 512MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者