一些树形 dp 问题

树上背包
给一个 n2000n \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 的每个儿子  vson(u)\forall \ v \in \text{son}(u)尝试合并到父节点上

具体来说,假设当前合并的儿子是 vv,要把它合并到父节点 uu 中,我们需要依次枚举 u,vu, v 子树中分别选 i,ji, j 个点
isz(u), jsz(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) 就表示合并子树 vuv \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,i1)+audp(u, i) \leftarrow dp(u, i-1) + a_u

树上背包2
给一个 nn 个点的有根树,每个点给个重量 wiw_iviv_i,求一个重量和恰好为 mm 的包括根的连通块
求最大权值
换句话说,如果一个点 uu 被选进来,从 urootu \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} 序中从 n1n \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,jwi)+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 nkk,假设有 ss 个数,满足 i=1s(ki)!n\displaystyle \sum_{i = 1}^{s} (k_i)! \leqslant n
然后进行状态压缩,用一个 ss 位的二进制数 stasta 表示,形如 (ki)!(k_i)! 的数选还是没选,枚举 [0,2s)[0, 2^{s}) 每个状态
那么 stasta11 的个数表示选了几个合法的可以表示成阶乘的数,这些数的和记为 sumsum

如果 nsumn \geqslant sum,说明还需要选一些形如 2d2^d 的数,这些形如 2d2^d 的数的和是 (nsum)(n - sum)
那么只要看 (nsum)(n - sum) 在二进制下有多少位为 11,记为 c=ones(nsum)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,其中 jjii 的相邻节点
一开始所有节点的权值 wiw_i 都是未知的,你要做的是为每一个节点分配一个权值 wiw_i,使得好节点的个数最大,如果有多个最优解
要求取权值和最小的那一个

先从简单的开始分析
假设树只有 22 层,即 11 个根节点 uukk 个儿子 <v1,v2,,vk>\left<v_1, v_2, \cdots, v_k \right>
此时最优解,一定要舍弃 uuuu 不是好节点,剩下的节点权值都和 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+1xkd = |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((xkxj)+d)=(xk+d)kj=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+1ksum\text{cost}(k+1) = x_{k+1} \cdot k - sum

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

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

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

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

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

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

另外需要注意的是,要保证 [ky,(y+1)k1][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_ior\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=(a1a2an)S = (a_1 \mid a_2 \mid \cdots \mid a_n)

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

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

(a1a2an)\begin{pmatrix} a_1 \\ a_2 \\ \vdots \\ a_n \end{pmatrix}

因为 coziness\text{coziness} 是求 szsz1n1 \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)++(n2k1)=2n1\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}

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

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

(2d1+2d2+,2dk)2n1=S2n1\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} 的最大值

注意到 ai5106a_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),ijdp(j) \to dp(i), \quad i \mid j,可以写出转移方程如下
dp(i)=maxijdp(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
这样对于 iji \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)=icnt(i)dp(i) = i \cdot \text{cnt}(i),表示 ii 之前不放更长的段 jj,前缀 gcd\text{gcd} 都是 ii
否则的话,ii 可以由更长的段 jj 转移过来,jj 剔除某些因子就得到了 ii,如上图所示
dp(i)=maxij(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=ifac(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} 都等于 iigcd(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
第二种就是找到 iji \mid jjjdp(i)dp(i) 可以由 dp(j)dp(j) 转移过来,即在 jj 的基础上,剔除某些因子

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

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

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

具体来说,从大到小 i[N1]\forall i \in [N \to 1],枚举素因子 pprimes, j=ipp \in \text{primes}, \ j = i * p
dp(i)=max(icnt(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) 即可