250215 模拟赛 T6 DAG 计数 题解
题目大意
给定一个 \(n\) 个点的有向图,问有多少个边集是无环的。
\(n\le 17\),\(m\le n\times(n - 1)\)
样例输入
样例输出
题解
这时一类经典的 DAG 计数问题。\(n\le 17\) 可以状压。注意到 DAG 可以分层,因此我们在考虑点集 \(S\) 的答案时,可以枚举 DAG 的最顶层,并向下转移。
考虑一种朴素的 DP:\(f_{i,j}\) 表示点集 \(i\) 中顶层为 \(j\)(入度为 \(0\))的方案数。通过枚举顶层 \(S_1\),并使 \(S_1\) 中的每个点向剩下的部分的顶层连至少一条边。这样的时间复杂度为 \(O(4^n)\),不能通过本题。
我们注意到记录顶层 \(j\) 产生了大量的状态,因此我们尝试去掉这一维度。但是如果直接去掉 \(j\),会产生重复的贡献。因为这样做去掉了顶层节点入度为 \(0\) 的限制,使得同一个 DAG 的分层方法不唯一。
错误的转移:
注意到 DAG 的形状只和其边集有关。而这种转移会统计到每种 DAG 的每一种划分方式。我们希望使得同一个 DAG(边集相同)的所有分层方案加起来只产生 \(1\) 的贡献。
我们尝试写出一个上式的等价形式,打散并重排贡献,使得同一个 DAG 不同的划分方式排在一起计算:
其中 \(G_i(V,E_i)\) 表示所有合法(无环)的 DAG。我们先集中考虑一个 DAG:
其中 \(V_1,V_2,\cdots,V_k\) 依次为最顶层到最底层的分层,因此需要保证 \(V_i\) 内部没有边。
我们希望 \((2)\) 式最终等于 \(1\),因此可以考虑容斥。这里我们引入一个重要结论:
对于
其中 \(m\) 是任意随机正整数(也就意味着可以不与 \(n\) 有关),而
则有 \(g(n)=(-1)^n\)。
证明
考虑使用数学归纳法,边界情况显然成立。假设对于全部 \(n<n_0\),都有 \(g(n)=(-1)^{n}\)。对于 \(g(n_0)\):
证毕。
我们可以逐步考虑 \(V_1,V_2,V_3\cdots\),将 \((2)\) 式化为递归的形式:
其中 \(V_1\) 入度为 \(0\),内部无边。
我们注意到 \(V_1\) 总是一个极大集合 \((V_1)_{\max}\) 的子集,选择的 \(|V_1|\) 满足 \(|V_1|\le |(V_1)_{\max}|\)。用集合的大小代替集合本身,则方案数形如一种组合数的形式。将 \(|(V_1)_{\max}|\) 作为无关变量 \(m\),\((4)\) 就拥有了和结论 \((3)\) 极为相似的结构:
但是我们注意到两者相差一个负号。将上式添加负号:
可以将所有负号从递归函数 \(g\) 中提出,还原到 \((2)\) 式中。不难发现负号的数量等于递归的深度,也即分层的数量 \(k\)。因此:
结果等于 \(1\),这正是我们想要的。但由于 \((6)\) 式难以编程计算(需要同时枚举边集 \(E\) 和划分方式 \((V_1,V_2,\cdots)\)),我们还使用 \((1)\) 式类似的结构。这其实很容易处理,根据 \((5)\) 式我们知道:每额外分一层 \(V_i\),就应该添加一个负号,而负号和连边的方案无关。因此直接在 \((1)\) 式上添加一个负号:
再带上剩下的 \((-1)^n\),则最终答案为
\((7)\) 式转移时间复杂度为 \(O(3^n)\)。
到这里 dp 的思路已经完成了。但仍有一个问题:如何 \(O(1)\) 查询 \(E(S_0,S_1)\)。显然如果不考虑空间我们可以 \(O(3^n)\) 预处理出答案,转移时查询即可。但是 \((3^n)\) 开不下数组,需要转换思路。
既然不能预处理,考虑转移的同时 \(O(1)\) 处理 \(E(S/S_0,S_0)\)。然而,\(S/S_0\) 和 \(S_0\) 都在动态变化,无法处理。因此我们需要调整转移顺序,使 \(S/S_0\) 和 \(S_0\) 中的一个固定住。
考虑刷表法。
具体的,记 \(S_1=S/S_0\)。我们考虑对每一个 \(S_1\) 枚举增量 \(S_0\),并从 \(S_1\) 转移到 \(S_1+S_0\)。这样查询的 \(E(S_1,S_0)\) 中就有一个点集 \(S_1\) 是固定的,使得每遍历到一个新的 \(S_0\),我们可以通过之前的 \(E(S_1,S_0')\) 用 \(O(1)\) 的时间转移到 \(E(S_1,S_0)\)。显然我们可以取 \(S_0'=S_0\oplus lowbit(S_0)\),通过预处理求出 \((S_1,lowbit(S_0))\) 产生的边界贡献。