2026.1.9日闲话:DAG 计数

1.9日闲话:DAG 计数

本人以后的学习笔记会尽量以闲话形式发出,避免对初学者造成困惑。

超前情提要:jijidawang 和 xrlong 强烈推荐主旋律。

前情提要:吃饭的时候,bjt 和 zxk 让我写主旋律,我果断接受了学习这道题的任务(

但是由于抄了好几天的题解了,于是自己手推了一下,和自己搏斗了 INF 年,最终成功击败主旋律,写此题解纪念一下。

DAG 容斥

不是,这东西到底有啥用啊?上不如集合划分容斥,下不如子集反演。

直接上子集反演推法了。

由于一个 DAG 具有良好的递归子问题性质,假设现在有一个 DAG,那么删除所有入度为 0 的点后还是一个 DAG,可以发现如果这样构造出 DAG 的话是不重不漏的。

下文的零度点指的就是入度为 0 的点。

那么有一个简单的思路就是枚举所有的零度点转移,剩下的单独构成一个 DAG,列出简单的一个式子,假设 \(v(S, T)\) 表示从集合 \(S\) 到集合 \(T\) 的连边方案数,那么有:

\[f_S = \sum_{\varnothing \subset T \subseteq S}v(T, S - T)f_T \]

显然我们的转移不可能从滚木集合转移过来。

其中 \(S - T\) 表示对称差。

可是这样实在是有点自欺欺人了,我们发现一件惊恐的事情,就是 \(T\) 里也可能有零度点。

假设现在处在 \(S\) 这个大的点集之下考虑,设 \(g_T\) 表示恰好 \(T\) 为零度点的方案数,\(h_T\) 表示钦定 \(T\) 为零度点的方案数。

由于:

\[h_T = \sum_{T \subseteq U \subseteq S}{g_U} = v(T, S - T)f_{S - T} \]

通过子集反演可得:

\[g_T = \sum_{T \subseteq U \subseteq S}{(-1)^{|U| - |T|}h_U} = \sum_{T \subseteq U \subseteq S}{(-1)^{|U| - |T|}v(U, S - U)f_{S - U}} \]

然后枚举一下零度点再推推式子就得到了:

\(\begin{aligned} f_S &= \sum_{\varnothing \subset T \subseteq S}{\sum_{T \subseteq U \subseteq S}{(-1)^{|U| - |T|}v(U, S - U)f_{S - U}}} \\ &= \sum_{\varnothing \subset U \subseteq S}v(U, S - U)f_{S - U}\sum_{\varnothing \subset T \subseteq U}{{(-1)^{|U| - |T|}}} \\ &= \sum_{\varnothing \subset U \subseteq S}v(U, S - U)f_{S - U}(-1)^{|U|}(\sum_{T \subseteq U}{{(-1)^{|T|}}} - 1) \\ &= \sum_{\varnothing \subset T \subseteq S}(-1)^{|T| + 1}v(T, S - T)f_{S - T} \end{aligned} \)

这就是经典的 DAG 容斥系数 \((-1)^{|T| + 1}\) 的一种推法了。

个人感觉对于这类问题是多变的,所以会一个好理解的简单做法现场重新推一遍是最好的,就比如下面的主旋律,其形式并不相同。

比起一些需要理解的做法,这个肯定是很简单的了。

我们可以追溯一下本质,其实这个 \(T\) 还是在钦定那些点作为零度点,所以其实还是需要乘上一个 \(w_T\) 表示零度点之间的贡献,不过现在讨论的阶段不存在这个系数。

Amusement Park

这题其实是需要一步转化的,就是发现对于一个合法的定向方式,其全部翻转后也是合法的,那么所有方案取平均值后就是方案数乘上 \(\frac{m}{2}\)。

其实上面那步是最难的。。。

然后就是板子了,发现 \(v(S, T)\) 其实是 1,因为已经定好向了。

然后 \(w_T\) 或者说是如果这个枚举的 \(T\) 不是一个独立集那就不能转移。

有标号 DAG 计数

注:含多项式内容。

首先要求弱联通,这点很没用,最后对 EGF 上发多项式 ln 即可。

假设 \(f_i\) 表示 \(i\) 个点构成的 DAG 方案数。

其中可以得到 \(v(S, T) = 2^{|S||T|}\)

套用上面的式子得到:

\[f_i = \sum_{j = 1}^{i}\binom{i}{j}f_{i - j}2^{ij} \]

这其实本质是将 \(f_S\) 压缩到了 \(f_{|S|}\) 这个状态里。

看着很想半在线卷积,不过还差点。

由于:\(ij = \binom{i + j}{2} - \binom{i}{2} - \binom{j}{2}\)

\[f_i = \sum_{j = 1}^{i}{\frac{i!}{j!(i - j)!}f_{i - j}\frac{2^{\binom{i}{2}}}{2^\binom{j}{2}2^\binom{i - j}{2}}} \]

更改一下就出现卷积形式了:

\[\frac{f_i}{i!2^{\binom{i}{2}}} = \sum_{j = 1}^{i}{\frac{1}{j!2^{\binom{j}{2}}}\frac{f_{i - j}}{(i - j)!2^{\binom{i - j}{2}}}} \]

现在得到了这样的形式:

\[F_i = \sum_{j = 1}^{i}{G_jF_{i - j}} \]

其中 \(G_0 = 0\) 。

如果你做过分治 FFT 或者常系数齐次线性递推的话。

发现 \(F * G\) 卷积之后只有 \(x^0\) 次项系数是错的,加上就行了。

\[F = F * G + 1 \]

\[F = \frac{1}{1 - G} \]

最后 ln 成答案就行。

复杂度 \(O(n\log n)\)。

主旋律

今日主角。

首先先不要慌,如果一个图不是强连通图,那么其一定是由一堆强连通分量构成的大于一个点的 DAG。

于是乎变成了 DAG 计数。

现在假设 \(f_S\) 表示 \(S\) 构成的方案数, \(a_S\) 表示 \(S\) 构成的强连通图个数。

此时转移时 \(v(S, T) = 2^{e(S, T)}\) 其中 \(e(S, T)\) 表示从 \(S\) 指向 \(T\) 的边数。

但此时 \(w_T\) 是有意义的了,因为前面说的那些都是零度点,可这里不一样了,这些是没有入度的强连通分量,所以假设我们现在钦定一堆强连通分量作为零度集团,那么可以自己推一下,根据子集反演可得:如果钦定了 \(cnt\) 个强连通分量,那么容斥系数应当是 \((-1)^{cnt + 1}\)。

如果先不管容斥系数的话,我们的 \(w_T\) 也是有良好的递归结构的,就是枚举新的强连通分量由那些点构成,但是这样会算重,所以需要钦定最小编号的点在新加入的强连通分量中。

而容斥系数有什么含义呢,其实就是我们现在新加入一个强连通分量,那么其贡献应当为:\(-1\) 而非 \(+1\)。

式子就是:

\[w_S = \sum_{\varnothing \subset T \subseteq S}{-a_Tw_{S - T}} \]

然后是 \(f_S\) 如何求得,由于容斥系数已经算过了,那么直接套用之前的式子:

\[f_S = \sum_{\varnothing \subset T \subseteq S}{w_Tf_{S - T}2^{e(T, S - T)}} \]

\[a_S = 2^{e(S, S)} - f_S \]

不过这里有一个小问题,就是 \(f_S\) 和 \(w_S\) 冲突了,但是简单调整一下发现,\(w_S\) 需要的 \(f_S\) 其实是 \(a_S\) 单独形成一个强连通分量,这是不能转移到 \(f_S\) 里的。

于是解决方案就是先不转移 \(f_S\) 对 \(w_S\) 的贡献,算完 \(f_S\) 后再算 \(w_S\)。

不过这复杂度是 \(O(3^nn^2)\) 的。

然后发现一点,就是 \(e(S, T)\) 其实等于:

\[e(S, T) = \sum_{i \in S}{e(\{i\}, T)} \]

然后预处理一下 \(e(\{i\}, T)\) 即可。

精细实现复杂度变成 \(O(3^nn)\) 或 \(O(3^n)\)。