对一类 DAG 问题的研究

同步发布于洛谷

第一次写这么长的文章,有问题清指出。

由于本人太弱,只能写一些自己关于这个东西的理解。也有很多很好的题目由于本人太菜没有做,后续做了可能会加上。

有好的题目也欢迎推荐,我可以尝试一下。

阅读本文章,需要有一定的图论和容斥基础。

1. 引入

我们考虑一个 DAG,点数很小,我们该如何刻画它;当它点数很大,我们又该怎么刻画?

显然,我们不能将边作为衡量的关键,因为 DAG 的边数是 \(\mathcal{O}(n^2)\) 的,加上每条边有 \(0/1\) 两种状态,状态总数就是 \(\mathcal{O}(2^{n^2})\) 的,显然不可以接受。

所以,我们应该从 DAG 本身的特性出发。

首先,我们知道 DAG 是能进行 \(\text{Toposort}\) 的,那么也就是说,我们一定能找到至少一个入度为 \(0\) 的点和出度为 \(0\) 的点。

这启发我们对 DAG 进行分层操作,或者说,我们每一次都把 DAG 中入度为 \(0\) 的点(或出度为 \(0\))的点删掉,将这些点当作一层,这样剩下来的图也是一个 DAG,这样就有了一个子结构。

形式化表达:
我们考虑给每一个点附上一个 \(\text{layer}\) 标记。初始时,我们将所有的入度为 \(0\) 的点的
\(\text{layer}\) 标记赋值为 \(0\),接下来进行 bfs 操作,每次我们将新搜到的点 \(u\)\(\text{layer}\) 值更新为 \(\text{layer}_v+1\),这样把 \(\text{layer}\) 相等的点放在一起,就能实现给 DAG 分层。(按出度分层同理)

下面记入度为 \(0\) 的点为 源点,出度为 \(0\) 的点为 汇点

2. DAG 计数的一般思路

2.1 推导

下面的推导都在 \(U\) 全集下的 \(S\) 子集进行。

\(f_S\) 表示在 \(S\) 下构造合法 DAG 的方案数。

直观的递推思路是选择一个非空子集 \(T \subseteq S\),强制将其中所有的元素为入度为 \(0\) 的点,那么剩下的 \(S \setminus T\) 必须是一个合法的 DAG。

这样就构造出了一个转移。

但是发现,\(T\) 确实所有的点入度为 \(0\),但是一旦和 \(S \setminus T\) 连起来后,\(S\setminus T\) 里面也会有入度为 \(0\) 的点,显然会算重。

现在我们就得考虑去重,也就是容斥。

考虑在两者合并后共有 \(m\) 个源点,且 \(T\) 的大小为 \(k\),设容斥系数为 \(c(k)\),那么有。

\[\sum\limits_{k=1}^{m}\binom{m}{k}c(k)=1 \]

由二项式定理知:

\[(1-1)^m = \sum\limits_{k=0}^{m}\binom{m}{k}(-1)^k \]

对比两式,很明显知:\(c(k)=(-1)^{k-1}\)

由此我们就能推出 DAG 计数的一般形式:

\[\begin{aligned} f_S=\sum\limits_{\emptyset \not=T \subseteq S} (-1)^{|T|-1} 2^{E(T,S\setminus T)} f(S\setminus T) \end{aligned} \]

其中 \(E(A,B)\) 表示从 \(A\) 连向 \(B\) 的边的数量。

2.2 例题

P6295 有标号 DAG 计数

由于这道题没有题目中给出的边,可以自由选择,所以有哪些点是不强相关的,因为点是可以置换的,我们可以先不管,最后再乘上一个 \(n!\) 即可。

也就是说,我们的状态完全没有必要设成集合,而是设成点的数量,也就是设 \(f_i\) 表示 \(i\) 个点构成 DAG 的方案数。

考虑上述转移。

\[ f_i=\sum\limits_{j=1}^{i} (-1)^{j-1}\binom{i}{j} 2^{j(i-j)} f_j \]

时间复杂度是 \(\mathcal{O}(n^2)\) 的。

而且这样会忽略一个细节,就是这个东西无法保证弱联通,这个先按下不表。

考虑组合意义(至少我是这样认为的),\(j(i-j)\) 看作在一堆 \(j\) 石子里面摸出一个,再在 \((i-j)\) 个石子里摸出一个的方案数,等价于再 \(j+(i-j)=i\) 个里面摸出两个,再减去再同一堆里面摸出两个的方案,用数学语言表达就是:

\[j(i-j)=\binom{i}{2}-\binom{i-j}{2}-\binom{j}{2} \]

将式子代回,得到:

\[f_i=\sum\limits_{j=1}^{i}(-1)^{j-1} \times \frac{2^{\binom{i}{2}}}{2^{\binom{j}{2}} 2^{\binom{j}{2}}} \times \frac{i!}{j!(i-j)!} \times f_j \]

观察发现,有一部分东西只和 \(i\) 有关,有一部分东西和 \(j\) 有关,有一部分东西和 \(i-j\) 有关。

按照上面的观察整理:

\[\frac{f_i}{2^\binom{i}{2}i!}=\sum\limits_{j=1}^{i}\frac{(-1)^{j+1}}{2^\binom{j}{2}j! }\times \frac{f_{i-j}}{(i-j)!\times\binom{i-j}{2} } \]

发现左边的式子明显和右边第二个式子同构。

但是我们还有弱联通这个条件没有解决。有个很经典的结论是 \(f\) 的 EGF 是 \(ans\)\(\operatorname{exp}\)
这完全提示我们使用 EGF 解决这道题。

\(F(x)=\sum\limits_{i=0}^{\infty}\frac{f_i}{2^\binom{i}{2}i!}\)\(G(x)=\sum\limits_{i=0}^{\infty} \frac{(-1)^{j+1}}{2^\binom{j}{2}j! }\)

显然有 \(F(x)=F(x)G(x)+1\)

那么 \(F(x)=\frac{1}{1-G(x)}\)

接下来就是很简单的操作了,我们设答案的生成函数为 \(Ans(x)\),由其组合意义知:\(\operatorname{exp}(Ans(x))=F(x)\),反过来 \(\ln(F(x))=Ans(x)\)

然后这道题就做完了。

代码就不放了,直接用多项式全家桶就行。

2.3 总结

这里我们推出来了 DAG 计数的基本形式,然后也做了一道 DAG 计数的板子题(虽然好像难点不再 DAG 上?),实际上,DAG 计数的基本思路就是:利用 DAG 的无环性质,通过钦定入度为 \(0\) 或者出度为 \(0\) 的点,将 DAG 分成若干层,构造出子结构进行计数。

3. DAG 计数题目

题目难度按本人主观难度递增。

下面的东西会参照 Codeforces 上的题解,给出若干 Hint。

3.1 [ARC221B] Two-Powered Sum

Atcoder

Luogu

场切题目。

  • Hint1
    考虑一下 \(A_i=x,A_j=y\),但是 \(x\) 的第 \(j\) 位为 \(1\) 的情况?

  • Hint2
    考虑将问题抽象成图,若出现上述的 \(x,y\),就连接一条 \(x\)\(y\) 的有向边。

  • Hint3
    考虑图的性质?

如 Hint2 当我们抽象成一张图,一条边 \((x,y)\) 表示 \(x\) 操作一定在 \(y\) 操作之前。

这里我们考虑从汇点出发,往源点删,为什么呢?因为汇点是无后效性的,它被删掉过后,不会影响前面的东西。

然后发现我们这里可以随意连边,所以集合信息是不重要的,考虑将集合维省略掉。设 \(f_i\) 表示目前使用 $i
个点组成的合法 DAG 的个数。

现在我们枚举一下汇点个数 \(j\),还有 \(j\) 覆盖到了多少个点 \(s\),那么显然分配的方案数为 \(\begin{Bmatrix} s \\ j \end{Bmatrix}\)

然后考虑连边的方式,发现每个点都能连出去 \(2^{n-s}\) 条边,所以总的方案数就是 \(2^{k(n-s)}\)

带入容斥,得到最终的转移方程:

\[ f_i= \sum_{s=1}^{i} \binom{i}{s} f_{i-s} \sum_{j=1}^{s} (-1)^{j-1} \left\{ \begin{matrix} s\\ j \end{matrix} \right\} 2^{j(i-s)}. \]

时间复杂度 \(\mathcal{O}(n^3)\)

3.2 P11714 [清华集训 2014] 主旋律

Link

写一个自己认为非常顺畅的思维过程吧,也是一个加强了对容斥的理解。

首先,SCC 这种东西是无法被刻画的(或者说很难去很好的刻画),因为它太过于整体化了,没有办法分割成为子问题继续计数。

此时想到正难则反,考虑对于整体不为 SCC 的图计数,那么考虑对于这个图缩点,得到的应该是一个点数大于二的 DAG。

好的,我们设 \(E_{S,T}\) 表示所有的从 \(S\) 集合连向 \(T\) 集合的边的数量,\(f_S\) 表示将 \(S\) 中所有的元素连成一个 SCC 的方案数,\(g_S\) 表示将 \(S\) 内的所有点连成若干个 SCC 且 两个 SCC 之间没有边 的按 SCC 个数带上容斥系数的和。

这里非常关键,因为当我们给 \(g_S\) 带上系数的时候,我们在后面的转移中不用关心 SCC 个数,只关心点集合了。

考虑点集 \(S\) 上的所有子图,总方案数为 \(2^{E_{S,S}}\) ,现在按照缩点后入度为 \(0\) 的 SCC 的并集分类。

若缩点后有 \(k\) 个入度为 \(0\) 的 SCC ,设他们的点集为 \(C_1,C_2,\dots C_k\),那么它们会在每个非空并里面被计算到 \(1\) 次,那么有:

\[\begin{aligned} 2^{E_{S,S}}&=\sum\limits_{\emptyset \not=T\subseteq S} g_T\times2^{E_{T,S/T}+E_{S/T,S/T}}\\ g_S&=2^{E_{S,S}}-\sum\limits_{\emptyset \not=T\subsetneqq S} g_T\times2^{E_{T,S/T}+E_{S/T,S/T}} \end{aligned} \]

现在我们已经完成了入度为 \(0\) 的 SCC 的容斥,现在我们应该考虑在 \(g_S\)\(f_S\) 里面去寻找两者的关联。

然后又是非常经典的套路了。

首先考虑 \(\operatorname{lowbit}(S)\) 这个点所在的集合 \(T\),首先,这个点会贡献一个 \(f_T\),然后 \(S/T\) 这个集合会提供一个带符号的 \(g_{S/T}\),由于多了一个集合,整体会带一个 \(-1\) 的容斥系数。

那么根据上面的分析,有:

\[\begin{aligned} g_S&=f_S-\sum\limits_{T\subsetneq S \land \operatorname{lowbit}(S)\in T} f_Tg_{S/T}\\ f_S&=g_S+\sum\limits_{T\subsetneq S \land \operatorname{lowbit}(S)\in T} f_Tg_{S/T} \end{aligned} \]

现在成功的建立了一个转移,但是需要注意的是,\(g\) 的递推用到了所有真子集,然后 \(f\) 的递推也用到了所有真子集,但是两者不依赖,可以先转移 \(g\) ,再用 \(g\) 转移 \(f\) ,实现的时候细节一点就行。

现在转移为 \(\mathcal{O}(3^n)\),但是我们还有一个 \(E\) 没有处理。

这个东西看上去是一个 \(4^n\) 的东西,其实使用 \(\operatorname{lowbit}\) 加上邻域就能做到 \(\mathcal{O}(3^n)\)

整体时间复杂度 \(\mathcal{O}(3^n)\)

3.3 P11834 [省选联考 2025] 岁月

Link

还没有做出来,补了会加进来。

posted @ 2026-06-12 09:52  To_string  阅读(9)  评论(0)    收藏  举报