网上的题解大多都比较简略,很多我认为并不显然的地方都缺少证明,这里写一篇详细的题解。
思路
首先考虑建出 \(\rm DAG\),若边 \((u,v)\) 存在需要满足 \(\text{dis}(p_u,p_v) \le |d_u-d_v|\),也就是 \(p_u\) 上的 \(f_u\) 条鱼可以转移到 \(p_v\) 上。
此时答案为这个 \(\rm DAG\) 的最小链覆盖(允许链有交)。
有定理:允许有交的最小链覆盖=最长反链(两两之间都没有路径)。
于是我们只要求出原来 \(\rm DAG\) 的最长反链即可。
不难发现最长反链一定是 \(u\) 里面一条鱼选了一定全选。
把最长反链上的点放到树上,我们得出以下结论:
若钦定根 \(root\) 不选,一个集合 \(S\) 内的点为反链,当且仅当:
- 对于 \(root\) 的所有子树 \(v\),\(S \cap \text{sub}(v)\) 是反链。
- 存在一个时刻 \(T\),满足对于所有 \((u,d) \in S\),都有 \(|T-d|<\text{dis}(u,root)\),也就是此时 \(u\) 不能走到根,根也不能走到 \(u\)。
简单证明一下:
第一个条件是显然的,因为 \(S\) 的所有子集必定都是反链。
第二个条件:
- 充分性:
对于两个 \(u,v\),满足 \(u,v\) 分属 \(root\) 的不同子树,那么 \(\text{dis}(u,v)>\text{dis}(u,root)+\text{dis}(v,root) \ge |T-d_u|+|T-d_v| \ge |d_u-d_v|\)。- 必要性:
设 \(u\) 在 \((l_u,r_u)\) 内不能走到根,那么如果 \(u,v\) 互相不能走到,显然需要满足 \((l_u,r_u) \cap (l_v,r_v) \neq \emptyset\),由于对所有 \((u,v)\) 都要满足,所以可以得到所有的 \((l_u,r_u)\) 交起来不能为空。
由于上面的结论钦定了根不选,所以我们要把 \(x\) 放到它的父亲那里考虑,于是我们建一个虚根 \(0\),连向 \(1\)。
考虑 \(\rm DP\),设 \(f_{x,i}\) 表示 \(x\) 子树内 \(x\) 不选,并且时刻 \(i\) 满足所有选的点都无法走到的最长反链,\(v\) 是 \(x\) 的一个儿子,\(l\) 是边 \((x,v)\) 的边权。
注意,此时的时刻 \(i\) 有可能是一个形如 \(x.5\) 的小数,我们可以把所有时刻都 \(\times 2\) 来规避这个问题。
然后考虑转移,设 \(g_{v,t}\) 表示点 \(v\) 给它的父亲 \(x\) 的 \(f_{x,t}\) 的贡献,此时有 \(g_{v,t}=\max_{i \in [t-l,t+l]} f_{v,i}\)。
解释一下这为啥对:
设 \(T\) 表示原来的 \(T\),\(T'\) 表示 \(T\) 有可能贡献到的时刻。
显然有 \(|T-d|<\text{dis}(u,root)\),也就是 \(T-d<\text{dis}(u,root)\) 且 \(d-T<\text{dis}(u,root)\)。
- \(T-d<\text{dis}(u,root)\),\(T'-d<\text{dis}(u,root)+l\),\(T'-d-l<\text{dis}(u,root)\),当 \(T'-d-l \le T-d\),也就是 \(T'-l \le T\) 时,一定满足。
- \(d-T<\text{dis}(u,root)\),\(d-T'<\text{dis}(u,root)+l\),\(d-T'-l<\text{dis}(u,root)\),当 \(d-T'-l \le d-T\),也就是 \(T \le T'+l\) 时,一定满足。
现在我们得到了若 \(T'-l \le T \le T'+l\),\(T\) 一定可以贡献到 \(T'\)。
现在你可能会有疑问:你这样难道不会漏掉一些合法的转移情况吗?
事实上,并不会漏掉,你可以自己手玩,最终发现漏掉的情况会在更大的 \(T\) 被转移到 \(T'\)。
然后我们考虑一下 \(v\) 上的那些鱼,它能加入的条件是 \(|T-d|<l\),也就是 \(d \in (T-l,T+l)\),我们发现跟上面的转移恰好差了 \(1\) 的范围,于是我们可以先令上面的转移转移 \(1\) 步,然后转移 \(v\) 上的鱼,然后再转移 \(l-1\) 步。
现在你可能又会疑惑:为啥能先转移一步,然后转移 \(l-1\) 步啊?
我们分析一下转移的本质,转移 \(x\) 步实际上是把差分数组所有正项向右移动 \(x\) 个单位,所有负项向左移动 \(x\) 个单位,如果正负项碰到了就抵消。
明白了转移的本质,我们便可以进一步优化,对于每个点 \(x\) 维护一个数据结构,这个数据结构内部维护差分数组,有一个堆维护正负项之间的距离,每次处理一个转移的时候就距离从小到大取出堆内的东西并合并,至于合并两个数据结构可以启发式合并,时间复杂度两只 \(\log\)。