Loading

[NOIP2022] 建造军营

前言

米奇妙妙 \(\rm{dp}\) , 也是高端计数

这种题看得懂想不出, 还是非常难蚌

能不能多想想再去看 \(\rm{TJ}\)

算法

思路 \(1\)

注意到除了割边, 其他的边都没有影响, 显然可以缩 \(\rm{e}\)-\(\rm{DCC}\) 再进行处理

这里发现缩完之后形成一棵树, 考虑树形 \(\rm{dp}\)

这里我有一个误区, 就是答案必须要是几个 \(dp_x\) 组合到一起, 但是实际上完全不用
对于树这种特殊结构, \(dp_{root}\) 已经包含了所有情况

因此令 \(dp_{x, 0/1}\) 表示只在 \(x\) 的子树之内建造军营, 军营的
首先考虑答案式子, 我们注意到, 答案应该是多种情况累加, 因为 \(dp_{x, 1}\) 实际上是不重不漏的, 主要需要处理的是那些无关紧要的边, 我们选择守或者不守
注意到如果缩完点之后当前子树的大小为 \(Size_x\) , 我们就有 \(Size_x - 1\) 条割边要守, 其中子树外的 \(m - Size_x + 1\) 条边可以任选
这里有一个超级巨大的问题: 对于当前节点到父节点的边, 一定不能选, 因为对于父节点, \(dp_{dpa, 1}\) 实际上考虑了这条边, 选上之后会导致重复, 因此记录答案时不能算这条边

事实上如果最早就有关于答案式的思路, 可以通过手推样例/观察性质发现这种性质, 但是我还是大为震撼

所以答案为

\[\sum_{i = 1, i \neq root}^{n} dp_{i, 1} \times 2 ^ {m - Size_x} + dp_{root, 1} \times 2 ^ {m - Size_x + 1} \]

太有实力啦

考虑递推

首先我们需要知道, 如果对于 \(x\) 的子树 \(son\) , 其中 \(son\) 内部有军营, 那么 \(x \to son\) 这条边必须要守, 其他无所谓

所以有

\[dp_{x, 1} \gets dp_{son, 1} \times dp_{x, 0} \]

\[dp_{x, 1} \gets dp_{son, 1} \times dp_{x, 1} \]

\[dp_{x, 0} \gets dp_{x, 0} \times dp_{son, 0} \times 2 \]

\[dp_{x, 1} \gets dp_{x, 1} \times dp_{son, 0} \times 2 \]

初始化

\[dp_{x, 0} \gets 1, dp_{x, 1} \gets 2 ^ {Siz_x} - 1 \]

注意这里表示的是只选 \(x\) 这一 \(\rm{e}\)-\(\rm{DCC}\) 中点的可能性, 反正可以随便选, 边随便选的情况在计算答案时统计


思路 \(2\)

考虑复习

感觉之前的做法过于巧妙, 遂决定重新理解一种更一般的做法

简化问题

首先理解题意

求选出点集 \(\mathbb{S}\) 和边集的方案数, 使得任意切断边集中的一条边, 点集中的点仍然联通

你发现如果切掉的不是割边, 一定没有用, 所以考虑先缩成一棵树, 这样留下来的边才有讨论价值
注意这样做会多出来一个 \(2^{|\mathbb{E}| - \epsilon}\) 的系数, 其中 \(\epsilon\) 表示割边的条数, 其意义是非割边随意保护或者不保护
还需要注意的是, 现在的一个点可能在原图中表示多个点, 这个再计算时是需要处理的
以下用 \(\varepsilon\) 表示该点在原图中代表多少点, \(\mu\) 表示该点在原图中包含了多少条边

好的, 经过上面的转换, 我们只需要解决这个问题的树上版本, 不难想到树形 \(\rm{dp}\)

分类讨论 + 转移

\(dp_{u, 0/1, 0/1}\) 表示对于 \(u\) 子树, 子树内是否有 \(\mathbb{S}\) 中的点, 子树外是否有 \(\mathbb{S}\) 中的点
第二个 \(0/1\) 方便合并时处理 \(u \to v\) 的边是否被保护

\(dp_{u, 0, 0}\)\(dp_{u, 0, 1}\) 的转移

不难发现

\[\begin{align*} dp_{u, 0, 0} \gets dp_{u, 0, 0} \times 2 dp_{v, 0, 0} \\ dp_{u, 0, 1} \gets dp_{u, 0, 1} \times 2 dp_{v, 0, 1} \end{align*}\]

表示 \(u \to v\) 可选可不选的方案数
最后处理 \(u\) 不选的方案数

\[\begin{align*} dp_{u, 0, 0} \gets dp_{u, 0, 0} \times 2^{\varepsilon} \\ dp_{u, 0, 1} \gets dp_{u, 0, 1} \times 2^{\varepsilon} \end{align*}\]

\(dp_{u, 1, 0}\) 的转移

你发现其必然可以从 \(dp_{u, 0, 0}\)\(dp_{u, 1, 0}\) 合并而来, 具体的

\[\begin{align*} dp_{u, 1, 0} \gets dp_{u, 0, 0} \times dp_{v, 1, 0} \\ dp_{u, 1, 0} \gets dp_{u, 1, 0} \times dp_{v, 1, 1} \end{align*}\]

合并最后处理

\[\begin{align*} dp_{u, 1, 0} \gets dp_{u, 0, 0} \times (2^{\varepsilon} \times 2^{\mu} - 2^{\varepsilon}) \\ dp_{u, 1, 0} \gets dp_{u, 1, 0} \times (2^{\varepsilon} \times 2^{\mu}) \end{align*}\]

分别表示 \(u\) 被选和可选可不选5

你发现这样子, 第三维的 \(0/1\) 会爆炸, 所以特殊转移
\(g_{u, 0/1}\) 表示确定合并的时候要选 \(v \to w\), \(f_{u, 0/1}\) 表示确定合并的时候不选 \(v \to w\) , 第二维意义不变

不难有

\[\begin{align*} f_{u, 0}, g_{u, 0} \gets dp_{u, 0, 0} \\ \ \\ f_{u, 1} \gets f_{u, 0} \times 2dp_{v, 1, 0} \\ g_{u, 1} \gets g_{u, 0} \times dp_{v, 1, 1} \\ g_{u, 1} \gets g_{u, 1} \times dp_{v, 1, 1} \\ \end{align*}\]

最终

\[\begin{align*} dp_{u, 1, 0} \gets f_{u, 0} \times (2^{\varepsilon + \mu} - 2^{\varepsilon}) \\ dp_{u, 1, 0} \gets g_{u, 1} \times (2^{\varepsilon + \mu} - 2^{\varepsilon}) \\ dp_{u, 1, 0} \gets f_{u, 1} \times (2^{\varepsilon}) \end{align*}\]

\(dp_{u, 1, 1}\) 的转移

你发现这种情况下, 无所谓 \(u\) 点选不选择, 对于 \(v\) 子树中包含 \(\mathbb{S}\) 中的点的情况, 都必须要选上 \(u \to v\)

\[\begin{align*} dp_{u, 1, 1} \gets dp_{u, 0, 1} \times dp_{v, 1, 1} \\ dp_{u, 1, 1} \gets dp_{u, 1, 1} \times dp_{v, 1, 1} \end{align*}\]

合并最后处理

\[\begin{align*} dp_{u, 1, 1} \gets dp_{u, 0, 1} \times (2^{\varepsilon} \times 2^{\mu} - 2^{\varepsilon}) \\ dp_{u, 1, 1} \gets dp_{u, 1, 1} \times (2^{\varepsilon} \times 2^{\mu}) \end{align*}\]


太乱了, 写了很久也不能用, 显然需要更具体一点, 上面的也不一定正确

合并子树 \((\)不考虑 \(u\) 节点\()\)

\(dp_{u, 0, 0}\)\(dp_{u, 0, 1}\)

不难发现

\[\begin{align*} dp_{u, 0, 0} \gets dp_{u, 0, 0} \times 2dp_{v, 0, 0} \\ dp_{u, 0, 1} \gets dp_{u, 0, 1} \times 2dp_{v, 0, 1} \end{align*}\]

\(dp_{u, 1, 1}\)

不难发现

\[\begin{align*} dp_{u, 1, 1} \gets dp_{u, 0, 1} \times dp_{v, 1, 1} + dp_{u, 1, 1} \times dp_{v, 1, 1} \end{align*}\]

\(dp_{u, 1, 0}\)

这个唯一比较复杂

首先是分成很多种情况

  • 没选 \(u\)
    • 只选一棵子树 \((\)此时 \(u\) 肯定不选\()\)
      考虑记这种情况的方案数为 \(f_{0/1}\) 表示是否选了子树
      转移类似 \(dp_{u, 1, 1}\)

      \[\begin{align*} f_0 \gets dp_{u, 0, 0} \\ f_1 \gets f_1 + f_0 \times 2dp_{v, 1, 0} \end{align*}\]

      需要注意的地方是, 初始化 \(f_0\) 时使用的 \(dp_{u, 0, 0}\) 应当是还没有被更新的

      然而你发现这样子「確有問題」, 具体在于除了最后一颗子树, 其他子树的系数 \(f_0\) 都不能表示「其他子树不选」, 所以我们需要对其进行修改
      观察 \(dp_{u, 0, 0}\) 的转移式, 我们考虑计算出 \(\displaystyle C = \prod \limits_{v \in \rm{son} (u)} 2dp_{v, 0, 0}\)
      转移变为

      \[\begin{align*} f_1 \gets f_1 + \frac{C}{2dp_{v, 0, 0}} \times 2dp_{v, 1, 0} \end{align*}\]

      进一步的, 直接令 \(f\) 表示 \(f_1\)

      \[\begin{align*} f \gets f + \frac{C}{dp_{v, 0, 0}} \times dp_{v, 1, 0} \end{align*}\]

    • 所有选了点的子树都和 \(u\) 有连边 \((\)此时 \(u\) 不选\()\) , 至少选两棵子树
      这种情况只能正难则反, 考虑减去不合法的情况, 即减去一个都没选的方案数, 再减去只选了一个的方案数, 同上转移, 仅仅把系数 \(2\) 去掉即可, 还有把第三维改成 \(1\)

  • \(u\)
    • 所有选了点的子树都和 \(u\) 有连边 \((\)此时 \(u\)\()\)
      每个儿子可以任意选或者不选

实现

框架

先计算各个边双联通分量的信息, 建好树之后如上开跑即可

代码

在此之前先用数据验证算法正确性

总结

神秘计数方法

\(\rm{dp}\) 式子考虑不到的, 可以在统计答案时考虑

注意计数不重不漏, 注意初始化


连通性问题常常要往缩点计数类的方向去想
缩点计数类问题一定要注意缩点带来的系数

一种合并类问题, 具体的套路在 之后的一篇文章 中有提到, 不在这里赘述

善于运用辅助数组避免新开一维或者更复杂的讨论

分类讨论能力必须加强, 考虑清楚每一维的含义

posted @ 2024-11-21 16:41  Yorg  阅读(97)  评论(0)    收藏  举报