一些树形 dp 问题

树上背包
给一个 n⩽2000n \leqslant 2000 的有根树,根节点为 11,每个点有权值 aia_i,下面询问
在 uu 的子树中选择一个大小恰好为 mm,并且包含 uu 点的连通块,求最大权值和

这类问题考虑子树合并
记 dp(u,i)dp(u, i) 表示以 uu 为根的子树,包括 uu,选择大小为 ii 的连通块,构成的最大权值和
首先 dp(u,1)=audp(u, 1) = a_u,考虑 uu 的每个儿子 ∀ v∈son(u)\forall \ v \in \text{son}(u),尝试合并到父节点上

具体来说,假设当前合并的儿子是 vv,要把它合并到父节点 uu 中,我们需要依次枚举 u,vu, v 子树中分别选 i,ji, j 个点
i∈sz(u), j∈sz(v)i \in \text{sz}(u), \ j \in \text{sz}(v),令 tmp(i+j)=max⁡(dp(u,i)+dp(v,j))\text{tmp}(i+j) = \max(dp(u, i) + dp(v, j))
那么 tmp(i+j)\text{tmp}(i+j) 就表示合并子树 v→uv \to u,连通块有 (i+j)(i+j) 个点,不包含 uu,构成的最大权值
然后令 dp(u,i+j)←tmp(i+j)dp(u, i+j) \leftarrow \text{tmp}(i+j)

子树全部合并完之后,此时 dp(u,⋯ )dp(u, \cdots) 是不包括 uu 这个点的权值的,那么很好办
将 dp(u)dp(u) 数组向右平移并且加上 aua_u 即可,dp(u,i)←dp(u,i−1)+audp(u, i) \leftarrow dp(u, i-1) + a_u

树上背包2
给一个 nn 个点的有根树,每个点给个重量 wiw_i 和 viv_i,求一个重量和恰好为 mm 的包括根的连通块
求最大权值
换句话说,如果一个点 uu 被选进来,从 u→rootu \to root 路径上的点也都要被选进去

如果像之前那样设计状态,dp(i,j)dp(i, j) 表示在 ii 子树中选择重量恰好为 jj 的连通块最大权值
那么可能出现的一种情况是,假设只有 11 个点,它的重量为 1000010000,那么虽然只有 11 个点,状态数却有 dp(1,10000)dp(1, 10000)
状态数可能是 O(nm)O(nm) 级别的,无法保证时间复杂度

树上问题很多时候我们考虑一个点选还是不选,从这一个角度出发
注意到如果一个点不会被选,那么这个点对应的所有子树也都不会被选进去,所以考虑树的 dfs\text{dfs} 序列
对于某个节点 uu,在 dfs\text{dfs} 序中,下标范围 [l(u)⋯r(u)][l(u)\cdots r(u)] 表示 uu 的子树
用 ne(u)\text{ne}(u) 表示 dfs\text{dfs} 序中跳过 uu 子树的下一个位置,也就是 r(u)+1r(u) + 1
如果下一个位置为空,记 ne(u)=n+1\text{ne}(u) = n+1

这里的状态设计,是在 dfs\text{dfs} 序中从 n→1n \to 1 倒着做,dp(i,j)dp(i, j) 表示 dfs\text{dfs} 序中 [i,n][i, n] 这一段的节点
选择重量和不超过 jj 的最大权值和,同时这些选择的点必须满足,如果一个点选上了,它的祖先也要全部选上

考虑边界 dp(n+1,0)=0dp(n+1, 0) = 0,其余的 dp(n+1,⋯ )=−∞dp(n+1, \cdots) = - \infty,这个边界表示 11 个点都没有选
一个点还是可以选或者不选,如果不选,整个子树跳过,否则状态会从子树转移过来

dp(i,j)=max⁡(dp(ne(i),j),dp(i+1,j−wi)+vi)\displaystyle dp(i, j) = \max(dp(\text{ne}(i), j), dp(i+1, j-w_i) + v_i)

codeforces

Codeforces Round #774 (Div. 2)

C. Factorials and Powers of Two
题目大意,如果一个数 xx 是合法的,当且仅当 xx 能写成 x=2dx=2^d 或者 x=d!x = d!,其中 dd 是非负整数
那么给你一个 nn,你要找到最小的 kk,使得 nn 等于 kk 个合法的数的和

先简单分析一下,1,21, 2 都是合法的,并且 n={1,2}n = \{1, 2\} 的话,答案 k=1k = 1
对于其他的 nn,不难想到,如果能写成合法数的和,那么它一定能提一个 22 这个公共因子,所以 nn 必须为偶数

n\displaystyle n 可以写成两部分的和,2d1+2d2+⋯+2dk\displaystyle 2^{d_1} + 2^{d_2} + \cdots + 2^{d_k} 和 ∑k(k!)\sum_k (k!)
第一部分很自然想到 nn 的二进制表示中,<d1,d2,⋯ ,dk>\left<d_1, d_2, \cdots, d_k \right> 这些位都要是 11

阶乘部分呢?可以枚举满足 k!⩽nk! \leqslant n 的 kk,假设有 ss 个数,满足 ∑i=1s(ki)!⩽n\displaystyle \sum_{i = 1}^{s} (k_i)! \leqslant n
然后进行状态压缩,用一个 ss 位的二进制数 stasta 表示,形如 (ki)!(k_i)! 的数选还是没选,枚举 [0,2s)[0, 2^{s}) 每个状态
那么 stasta 中 11 的个数表示选了几个合法的可以表示成阶乘的数,这些数的和记为 sumsum

如果 n⩾sumn \geqslant sum,说明还需要选一些形如 2d2^d 的数,这些形如 2d2^d 的数的和是 (n−sum)(n - sum)
那么只要看 (n−sum)(n - sum) 在二进制下有多少位为 11,记为 c=ones(n−sum)c = \text{ones}(n - sum)
取 min⁡(c+ones(sta))\min(c+\text{ones}(sta)) 就是答案

D. Weight the Tree
题目大意,节点个数为 nn 的无根树,一个节点是好节点,当且仅当这个点的权值 wiw_i 等于所有和它相邻的点的权值和
wi=∑jwjw_i = \sum_j w_j,其中 jj 为 ii 的相邻节点
一开始所有节点的权值 wiw_i 都是未知的,你要做的是为每一个节点分配一个权值 wiw_i,使得好节点的个数最大,如果有多个最优解
要求取权值和最小的那一个

先从简单的开始分析
假设树只有 22 层,即 11 个根节点 uu 和 kk 个儿子 <v1,v2,⋯ ,vk>\left<v_1, v_2, \cdots, v_k \right>
此时最优解,一定要舍弃 uu,uu 不是好节点,剩下的节点权值都和 wuw_u 相等
即 w(u)=w(v1)=w(v2)=⋯=w(vk)=1w(u) = w(v_1) = w(v_2) = \cdots = w(v_k) = 1

这样来看,最优解的方案是,不是好节点的权值都放 11,好节点的权值为其邻居的个数
我们需要求出最多有多少个好节点

考虑最优解中选择了哪些点作为好节点,对于节点 uu,如果选择了 uu 作为好节点,那么 uu 的子节点都不是好节点
如果 uu 不选,状态可以从子节点转移过来

这是比较典型的树形 dpdp,用 dp(u,0),dp(u,1)dp(u, 0), dp(u, 1) 分别表示 uu 这个点,不选为好节点或者选的时候
uu 及其子树中最多有多少个好节点

具体转移如下,dfs(u)\text{dfs}(u)
dp(u,0)=0,dp(u,1)=1dp(u, 0) = 0, dp(u, 1) = 1,然后遍历子节点 dfs(v)\text{dfs}(v)

{dp(u,1)+=dp(v,0)dp(u,0)+=max⁡(dp(v,1),dp(v,0))\displaystyle \begin{cases} dp(u, 1) += dp(v, 0) \\ dp(u, 0) += \max(dp(v, 1), dp(v, 0)) \end{cases}

因为题目中还需要输出权值和,所以 dpdp 数组存储一个二元组 (c,w)(c, w)
cc 表示最多的好节点的个数,ww 表示权值

此外还需要输出方案,所以我们要记录每一个节点的状态,以及它从哪里转移过来的,要记录一下 (u,sta)(u, sta)
再做一次 dfs\text{dfs},如果当前状态是 (u,1)(u, 1),那么将 w(u)=g[u].sizew(u) = g[u].\text{size},并且递归到 (v,0)(v, 0)
否则 w(u)=1w(u) = 1 并看看 dp(v,0),dp(v,1)dp(v, 0), dp(v, 1) 哪个大,就递归到哪个状态

Codeforces Round #775
C. Weird Sum
题目大意,给你一个 n×mn \times m 的矩阵,如果矩阵中两个元素相同,那么就将其曼哈顿距离加入到 sum\text{sum} 中
sum\text{sum} 初始为 00

由于是曼哈顿距离,行和列可以单独处理
首先遍历每一个点,并且用一个数组 r(v)r(v) 记录 vv 这个值出现的所有行的位置,c(v)c(v) 记录这个值出现的所有列的位置

以行 r(v)r(v) 计算为例,先对 r(v)r(v) 排序
假设目前已经计算好了 [1,k][1, k] 这 kk 个点的答案,考虑放入第 k+1k+1 个点,对答案 ansans 的贡献
假设第 k+1k+1 个点在位置 xk+1x_{k+1},那么 ans+=cost(k+1)ans += \text{cost}(k+1),cost(k+1)\text{cost}(k+1) 表示放入第 k+1k+1 个点的贡献

下面考虑如何计算 cost(k+1)\text{cost}(k+1),记 d=∣xk+1−xk∣d = |x_{k+1} - x_k|
加入第 k+1k+1 个点,其贡献可以基于第 kk 个点的情况来计算,比如求 (xj,xk+1)(x_j, x_{k+1}) 点对的距离
我们之前遍历求出过 (xj,xk)(x_j, x_k) 的距离,在此基础上,(xj,xk+1)(x_j, x_{k+1}) 的距离为 (xj,xk)+d(x_j, x_k) + d

cost(k+1)=∑j=1k((xk−xj)+d)=(xk+d)∗k−∑j=1kxj\displaystyle \text{cost}(k+1) = \sum_{j = 1}^{k} \left((x_k - x_j) + d \right) = (x_k + d) * k - \sum_{j=1}^{k}x_j\

其中 ∑j=1kxj\sum_{j = 1}^{k} x_j 用一个前缀和 sumsum 维护,xk+d=xk+1x_k + d = x_{k+1},综上所述
cost(k+1)=xk+1⋅k−sum\text{cost}(k+1) = x_{k+1} \cdot k - sum

列也是同样计算,把答案都加起来就可以了

D. Integral Array
题目大意,给定一个可重集 SS,如果满足 ∀x,y∈S,x⩾y\forall x, y \in S, \quad x \geqslant y,都有 ⌊xy⌋∈S\displaystyle \left\lfloor \frac{x}{y} \right\rfloor \in S
那么可重集是封闭的,现在需要判断可重集是否封闭
满足所有元素大小 1⩽si⩽c1 \leqslant s_i \leqslant c, c⩽106c \leqslant 10^6

算法设计
首先 ⌊xy⌋=k,1⩽k⩽106\displaystyle \left\lfloor \frac{x}{y} \right\rfloor = k, \quad 1 \leqslant k \leqslant 10^6
kk 的范围并不是很大,可以考虑枚举 kk

对每一个 kk,我们有 ky⩽x⩽(y+1)k−1ky \leqslant x \leqslant (y+1)k-1,那么如何确定 yy
先看看暴力枚举怎么样?对于 yy,枚举 k⋅y⩽ck\cdot y \leqslant c,也就是说只需要枚举 ck\displaystyle \frac{c}{k} 个数

这样看来时间复杂度是 ∑i=1nni=n∑i=1n1n=O(nln⁡n)\displaystyle \sum_{i = 1}^{n} \frac{n}{i} = n \sum_{i = 1}^{n} \frac{1}{n} = O(n \ln n)

完全没有问题,接下来只要知道存不存在 x∈[ky,(y+1)k−1]x \in [ky, (y+1)k-1] 的数就可以了
只需要预处理数组,标记一下每一个数是否出现,出现的话 cnt\text{cnt} 就 +1+1,然后用前缀和维护,保证区间的和 ≠0\neq 0 就可以了

另外需要注意的是,要保证 [ky,(y+1)k−1][ky, (y+1)k-1] 被区间 [1,c][1, c] 所包含,取一个交集

Codeforces Round #757 (Div. 2)
Divan and bitwise operations
题目大意,对于 nn 个非负整数构成的序列,a1,a2,⋯ ,ana_1, a_2, \cdots, a_n,定义一个 conziness\text{conziness} 如下
选出这个序列 aa 的所有非空子序列,计算出所有子序列的异或 xor\text{xor} 值,然后把这些异或值加起来,就是 conziness\text{conziness} 值
假设非空子序列的大小为 sz\text{sz},那么我们需要 <a1,a2,⋯an>\left<a_1, a_2, \cdots a_n \right> 中所有大小为 sz\text{sz} 的子序列
对于每一个大小为 sz\text{sz} 的子序列,我们取 S(sz)=(a(p1)⊕a(p2)⊕a(psz))S(sz) = \displaystyle (a(p_1) \oplus a(p_2) \oplus a(p_{sz}))
然后呢?coziness\text{coziness} 值等于 ∑sz=1nS(sz)\displaystyle \sum_{sz = 1}^{n} S(sz)

现在问题是,弄丢了 coziness\text{coziness} 值,但是给你 mm 个子序列,保证 aa 的任意子序列都在 mm 个子序列中出现至少一次
当然给出的并不是 mm 个子序列的具体值,而是子序列 (假设是 <a1,a2,⋯ ,asz>\left<a_1, a_2, \cdots, a_{sz} \right>) 中 aia_i 的 or\textbf{or} 值

现在问原来串对应的 coziness\text{coziness} 值是多少,当然 coziness\text{coziness} 不止一种,输出任意一种即可

算法分析
首先,不难想到将 mm 个数 <x1,x2,⋯ ,xm>\left<x_1, x_2, \cdots, x_m \right> or\textbf{or} 起来,就等于 S=(a1∣a2∣⋯∣an)S = (a_1 \mid a_2 \mid \cdots \mid a_n)

下面难点在于分析 SS 和 coziness\text{coziness} 有什么关系?对 SS,可以一位一位地来看
coziness\text{coziness} 要求所有非空子序列都要求一个 xor\textbf{xor} 和,并且加起来,我们尝试构造 coziness\text{coziness}

如果 SS 的某一位,假设第 dd 位为 11,那么 nn 个数的第 dd 位情况如下

(a1a2⋮an)\begin{pmatrix} a_1 \\ a_2 \\ \vdots \\ a_n \end{pmatrix}

因为 coziness\text{coziness} 是求 szsz 从 1→n1 \to n 所有子序列的异或值,然后再把异或值加起来
也就是说,我们可以构造 coziness\text{coziness},让 <a1,⋯ ,an>\left<a_1, \cdots, a_n \right> 第 dd 位都是 11

然后当 szsz 大小为奇数的时候,这一位异或起来还是 11,对 coziness\text{coziness} 有 (nsz)\displaystyle \binom{n}{sz} 的贡献
因为可以任意取 (nsz)\displaystyle \binom{n}{sz} 的子序列嘛,这个位的 11 被计算了 (nsz)\displaystyle \binom{n}{sz} 次

那么 dd 这一位贡献是 ∑sz=1n[sz是奇数]⋅(nsz)=(n1)+(n3)+⋯+(n2k−1)=2n−1\displaystyle \sum_{sz = 1}^{n} [sz \text{是奇数}] \cdot \binom{n}{sz} = \binom{n}{1} + \binom{n}{3} + \cdots + \binom{n}{2k-1} = 2^{n-1}

这个很好证明的,(1−1)n(1-1)^n 的二项展开,即可证明二项式奇数项和偶数项的和相等,和是 2n/2=2n−12^{n} / 2 = 2^{n-1}

最后,因为 coziness\text{coziness} 是求和,所以实际上 dd 这一位对应的值是 2d⋅2n−12^{d} \cdot 2^{n-1}
那么综上所数,每一位都考虑过去,仅考虑 SS 中为 11 的位 <d1,d2,⋯ ,dk>\left<d_1, d_2, \cdots, d_k \right>

(2d1+2d2+⋯ ,2dk)⋅2n−1=S⋅2n−1\displaystyle (2^{d_1} + 2^{d_2} + \cdots, 2^{d_k}) \cdot 2^{n-1} = S \cdot 2^{n-1}

D1. Divan and Kostomuksha

定义一个序列 aa,其权值为 ∑i=1ngcd(a1,a2,⋯ ,ai)\displaystyle \sum_{i = 1}^{n} \textbf{gcd}(a_1, a_2, \cdots, a_i),现在可以重排 aa
求 gcd\text{gcd} 的最大值

注意到 ai⩽5⋅106a_i \leqslant 5 \cdot 10^6,并不是很大,思考的方向是 aia_i 的因子
gcd(a1)→gcd(a1,a2)→⋯→gcd(a1,a2,⋯ ,an)gcd(a_1) \to gcd(a_1, a_2) \to \cdots \to gcd(a_1, a_2, \cdots, a_n) 的过程,gcdgcd 大致变化如图所示
757-d

用 dp(i)dp(i) 表示含有因子 ii 的前缀 gcd\text{gcd} 和的最大值,从大到小枚举每一个因子,考虑转移
dp(j)→dp(i),i∣jdp(j) \to dp(i), \quad i \mid j,可以写出转移方程如下
dp(i)=max⁡i∣jdp(j)+cost(i)\displaystyle dp(i) = \max_{i \mid j} dp(j) + \text{cost}(i),其中 cost(i)\text{cost}(i) 表示加入 ii 这个因子的代价

下面来看 cost(i)\text{cost}(i) 怎么计算
可以先预处理原数组,对于原数组每一个数 xx,枚举 xx 的所有因子 ii,记一下 ii 出现次数,cnt(i)+=1\text{cnt}(i) += 1
这样对于 i∣ji \mid j,那么只要 jj 出现了,ii 这个因子也一定会出现,这部分在 dp(j)dp(j) 的时候已经统计了

考虑 dp(i)dp(i), 在答案中加入 ii 这个因子,贡献增加了多少?
那么 ii 这个因子作为 gcd\text{gcd} 又单独出现了 cnt(i)−cnt(j)\text{cnt}(i) - \text{cnt}(j) 次
所以 cost(i)=(cnt(i)−cnt(j))⋅i\text{cost}(i) = (\text{cnt}(i) - \text{cnt}(j)) \cdot i

从大到小 dpdp,先令 dp(i)=i⋅cnt(i)dp(i) = i \cdot \text{cnt}(i),表示 ii 之前不放更长的段 jj,前缀 gcd\text{gcd} 都是 ii
否则的话,ii 可以由更长的段 jj 转移过来,jj 剔除某些因子就得到了 ii,如上图所示
dp(i)=max⁡i∣j(dp(i), dp(j)+i⋅(cnt(i)−cnt(j)))\displaystyle dp(i) = \max_{i \mid j} (dp(i), \ dp(j) + i \cdot (\text{cnt}(i) - \text{cnt}(j)))

算法实现上,jj 也可以枚举,只需要打表所有的因子,然后枚举出现过的因子 fac(k)\text{fac}(k)
j=i∗fac(k)j = i * \text{fac}(k),用两重循环判断即可

另外注意,打表因子的时候,对于每个数 xx,其重复的因子只需要记一次
因为我们打表的是因子而不是素因子,比如 x=4x = 4,我们 cnt(2)=1\text{cnt}(2) = 1 而不是 22
之后我们在 cnt(4)\text{cnt}(4) 的时候再记录 cnt(4)=1\text{cnt}(4) = 1,这样所有因子都可以不重不漏记下来

最后的答案是考虑所有因子,如果 cnt(i)=n\text{cnt}(i) = n,也就是说这个因子在所有数中都出现的话
那么 dp(i)dp(i) 就是边界了,用 dp(i)dp(i) 去更新答案

总结,这个类型的 dpdp 大致就是这样,枚举每一个因子 ii,有下面两类决策转移
第一就是我使得前缀 gcd\text{gcd} 都等于 ii,gcd(a1)=gcd(a1,a2)=⋯gcd(a1,⋯ ,ak)=igcd(a_1) = gcd(a_1, a_2) = \cdots gcd(a_1, \cdots, a_k) = i
对应到图上就是 ii 之前不放更长的段 jj
第二种就是找到 i∣ji \mid j 的 jj,dp(i)dp(i) 可以由 dp(j)dp(j) 转移过来,即在 jj 的基础上,剔除某些因子

D2. Divan and Kostomuksha (hard version)
和上一道题的区别仅仅是数据范围的不同

考虑如何优化,实际上,上一道题目对 i∣ji \mid j 时 dp(j)→dp(i)dp(j) \to dp(i) 的转移处理中,有重复计算
我们枚举了所有可能的因子,令 j=i⋅fac(k)j = i \cdot \text{fac}(k),实际上并没有必要

对于某一个素因子 pp,我们计算 dp(i)←dp(i⋅p)dp(i) \leftarrow dp(i \cdot p) 转移的时候,是没有必要再考虑 dp(i⋅p2)dp(i \cdot p^2) 的
因为我们从大到小 dpdp,考虑 dp(j)=dp(i⋅p)dp(j) = dp(i \cdot p) 的时候,肯定已经计算了 dp(j⋅p)=dp(i⋅p2)dp(j \cdot p) = dp(i \cdot p^2) 的转移了
所以我们只需要打表所有的素因子,然后从大到小 dpdp
对于转移 dp(i)←dp(j),i∣jdp(i) \leftarrow dp(j), \quad i \mid j,我们只考虑枚举所有可能的素因子,j=i⋅prime(k)j = i \cdot \text{prime}(k)
dp(j)→dp(i)dp(j) \to dp(i) 的转移,每次只剔除一个素因子

具体来说,从大到小 ∀i∈[N→1]\forall i \in [N \to 1],枚举素因子 p∈primes, j=i∗pp \in \text{primes}, \ j = i * p
dp(i)=max⁡(i⋅cnt(i), dp(j)+i⋅(cnt(i)−cnt(j)))\displaystyle dp(i) = \max(i\cdot \text{cnt}(i), \ dp(j) + i\cdot (\text{cnt}(i) - \text{cnt}(j)))

这里的素数打表到 max⁡(ai)\max(a_i) 即可