集合幂级数在子图计数上的应用

定义与基本运算

和生成函数差不多,把指数上的东西换成集合,可以这样简单理解集合幂级数。

需要定义运算,加法很简单,指数相同的,系数相加。

乘法定义为指数上的无交并的积之和:\([x^s](f* g)=\sum\limits_{t \subset s}[x^t]f \times [x^{s/t}]g\)

快速计算集合幂级数的乘法,其实也就是子集卷积。

如果直接写或 FWT 是不对的,会多算,我们再引入一个占位元 \(y\),把无交并可化为:\(s|t=u,|s|+|t|=|u|\),这个条件是冲要的。

换成形式化的就是 \([x^s y^{|s|}](f*g)=\sum\limits_{t \subset s}[x^t y^{|t|}]f \times [x^{s/t} y^{|s|-|t|}]\)

你可以理解成原本的 FWT 中都是对整数进行的运算,现在把整数换成多项式,FWT 中只有加减法,因此多项式每一位独立。

先做 FWT,这样对于 \(x\) 的乘法全部为对位乘法,不同指数间相互独立,然后对于 \(y\) 做正常的多项式乘法即可。

函数复合

函数的复合主要是求逆,\(\operatorname{exp,ln}\)

具体来说设 \(F(x)\) 是一个集合幂级数,我们需要求出来 \(\frac{1}{F(x)},e^{F(x)},ln(F(x))\)

因为 \(x\) 元上的集合卷积很麻烦,我们直接把 \(F(x)\) 变换成 \(\operatorname{FWT}(F(x))\),之后我们只需要考虑每一位上的普通多项式的运算。

由于 \(n\) 很小,我们只需要 \(n^2\) 算上面的三种运算。

以下简写 \([x^n]F(x)\)\(F_n\)

求逆:

\[B(x)=\frac{1}{A(x)} \]

\[B(x)A(x)=1 \]

两边系数拿出来作比较,设为 \([x^n]\)

\[\sum\limits_{i=0}^{n}B_iA_{n-i}=[n=0] \]

容易写出递推式:

\[B_0=\frac{1}{A_0} \]

\[B_n =-\frac{1}{A_0}\sum\limits_{i=0}^{n-1}B_iA_{n-i} \]

\(\operatorname{Exp}\):

\[B(x)=e^{A(x)} \]

两边同时求导:

\[B'(x)=e^{A(x)}A'(x)=B(x)A'(x) \]

只需要多项式乘法,然后再积分回去就好了。

不过可以通过比较系数法一步到位,比较两边的 \([x^n]\)

\[(n+1)B_{n+1}=\sum\limits_{i=0}^{n}B_i \times (n-i+1)A_{n-i+1} \]

写出递推式:

\[B_{n}=\frac{1}{n}\sum\limits_{i=0}^{n-1}B_i (n-i)\times A_{n-i} \]

好看一点,将 \(i\) 换成 \(n-i\)

\[B_n=\frac{1}{n} \sum\limits_{i=1}^{n}B_{n-i} A_i\times i \]

\(\operatorname{Ln}\):

\[B(x)=\operatorname{ln}(A(x)) \]

两边同时求导:

\[B'(x)=\frac{1}{A(x)}A'(x) \]

一个显然的做法是先求逆做多项式乘法再积分回去。

比较系数法仍然是可以获得更小常数的递推做法:

化为 \(B'(x)A(x)=A'(x)\),比较 \([x^n]\)

\[\sum\limits_{i=0}^{n}(i+1) \times B_{i+1} A_{n-i}=(n+1) \times A_{n+1} \]

\[(n+1) \times B_{n+1}A_0=(n+1)\times A_{n+1}-\sum\limits_{i=0}^{n-1}(i+1) \times B_{i+1} A_{n-1-i} \]

\[B_{n}=\frac{1}{n \times A_0}(n \times A_{n}-\sum\limits_{i=0}^{n-2}(i+1) \times B_{i+1} A_{n-1-i}) \]

好看一点,求和部分用 \(i\) 替换 \(i+1\)

\[B_{n}=\frac{1}{n \times A_0}(n \times A_{n}-\sum\limits_{i=1}^{n-1}i \times B_{i} A_{n-i}) \]

默认 \(A_0=1\),因此可以直接写为:

\[B_{n}=A_n-\frac{1}{n}\sum\limits_{i=1}^{n-1}i \times B_{i} A_{n-i} \]

以上复杂度均为 \(O(2^n n^2)\),如果不需要,则可以直接拿子集枚举的式子,然后求逆用比较系数法,\(\operatorname{exp}\)\(\operatorname{ln}\) 用组合意义就可以了,不再赘述。

常见复合的组合意义:

\(\operatorname{exp}\):

\(\operatorname{exp}\) 的展开式为 \(\sum\limits_{i \ge 0}\frac{F^i(x)}{i!}\)

\(F^i(x)\) 在集合幂级数中表示 \(i\) 个不交集合权值的乘积,而除以 \(i!\) 表示去掉方案。

则组合意义为由若干个无序不交集合组合而成,其中“组合”指的是权值的乘积。

\(\operatorname{ln}\):

\(\operatorname{ln}\) 作为 \(\operatorname{exp}\) 的逆运算,其组合意义就是将某个集合拆解为若干无序不交集合,这些集合的乘积为当前集合的权值。

更具体的意义在例题中体现:

半在线改造:

这个其实是容易的,因为我们只会从集合大小小的向大的转移。

所以我们求占位多项式的第 \(i\) 位的时候,只会用到 \(i-1\) 以下位的信息,这样我们就算出来了在线数组 \(\operatorname{FWT}f_i\)

[WC2018] 州区划分

应用:

联通性限制:

思路:直接算不要求联通的方案数,然后尝试用联通的通过 \(\operatorname{exp}\) 组合出任意的方案数,这样我们求 \(\operatorname{ln}\) 就好了。

联通子图计数

你学的容斥方法的本质是 ln。

\(F(x)\) 表示联通生成子图的集合幂级数,我们发现对于所有图形我们都可以用若干联通子图来表示,且不同联通子图互不影响,具体来说设 \(G(x)\) 表示原图生成子图的集合幂级数,则 \(G(x)=\operatorname{exp}(F(x))\)

\(G(x)\) 是容易的,\([x^s]G(x)=2^{\sum\limits_{(u,v) \in E}[u,v \in s]}\)

直接求\(\operatorname{ln}\) 就好了。

练习:

  • 「雅礼集训 2019 Day8」union
    模板题,联通图权值 exp = 任意图权值。直接求 \(\operatorname{ln}\) 即可。

  • 联通二分子图计数

    你学的容斥是在暴力求 ln

    一个想法是枚举黑点和百点的点集,要求内部没有边,中间随便连。但是这样并没有考虑联通的限制,现设生成的集合幂级数为 \(G(x)\)。思考这样算出来的到底是什么。

    其实是原图所有生成二分子图黑白染色的方案数量。我们仍然尝试用联通块的组合来描述。设 \(F(x)\) 为联通生成二分子图的集合幂级数,其中系数为方案数。注意到若一个子图有 \(k\) 个联通块,则黑白染色的方案数应该是 \(2^k\),因此我们给 \(F(x)\) 乘二再\(\operatorname{exp}\)起来,就可以得到 \(G(x)\),我们直接把 \(G(x)\)\(\operatorname{ln}\) 再除二就好了。

  • 联通欧拉子图计数

    欧拉回路的限制为所有点度数均为偶数。容易发现联通欧拉子图 \(\operatorname{exp}\) 后就变成了不联通的。

    快速求解不联通的:抽象为:有多少种选边的方案,满足边的异或为 0。可以线性基解决,把点集内的边拉出来插到线性基里面,总数-秩的个数的二的次幂就是答案。

    然后 \(\operatorname{ln}\) 回去就好了。

DAG 上:

思路:枚举没有入度的部分(缩点后),赋权容斥系数 \((-1)^{k+1}\),得到一个子集枚举形式的恒等式。

DAG 定向计数

DAG 是具有子结构的东西:去掉 0 入度的点后,剩下的部分仍然是一个 DAG。

考虑枚举 0 入度点的集合,要求这部分是个独立集。但是发现一个 DAG 中的零入度点集合会被所有非空子集枚举到,因此设 \((-1)^|s|\) 为容斥系数可以保证只算一次。

形式化:\(f_s=\sum\limits_{t \subset s,t \not= \emptyset}(-1)^{|t+1|}f_{s/t}, t 为独立集\)

然后发现其实就是设一个集合幂级数 \(F(x)\),其中每项的系数是 \((-1)^{|s|}\),由于这个你每次扣掉的独立集之间是有顺序的,所以并不是\(\operatorname{exp}\),而是直接 \(\sum\limits_{i \ge 0}F^{i}(x)\),这个东西收敛后是 \(\frac{1}{1-F(x)}\),集合幂级数求逆就行了。

强连通子图计数

一张有向图,缩点后就是 DAG。按照思路走,我们枚举缩完点后的0入度强连通分量集合 \(S\),他的贡献应该是枚举分解的形态:\(G_S=\sum\limits_{T_1,T_2,……,T_k}(-1)^k \prod\limits_{i=1}^{k}f_{T_i}\)。这些强连通分量之间没有边,然后这部分和剩下的点之间的边必须是顺序边,剩下点内部边可以任意。这样算的是整张图随便连的个数。

我们设 \(cross(S,T)\) 表示 \(S \rightarrow T\) 有向边的数量,用形式化的语言描述上面的东西就是:

\[\sum\limits_{t \subset s}G_t \times 2^{cross(t,s/t)+cross(s/t,s/t)}=2^{cross(s,s)} \]

观察这个 \(G\),发现他是无序组合的形式,用生成函数描述就是 \(G(x)=\sum\limits_{i \ge 0}\frac{(-1)^i \times f^i(x)}{i!}=\operatorname{exp}(-f(x))\)

换句话说 \(-f(x)=\operatorname{ln}(g(x))\),我们的目标是求出 \(g\)。这个包含 \(g\) 的等式是个子集卷积,但是系数却是不独立的,只能暴力做了。直接比较系数法就行了。

与之相匹配的,我们也只需要暴力求 \(\operatorname{ln}\) 就好了。

练习:

  • 2024联合省选 D2T2 重塑时光

    经典!
    把整个问题拆解:

    1. 如果确定了拆开的段数,有多少种方法拆到这个段数。
    2. 有多少种拆成固定段的满足题目要求的形态。

    先解决第一个问题,相对来说比较简单,由于段之间可以重拍,因此非空段间有顺序,空段间没顺序。于是先乘上 \(i!\),然后再和剩下的 \(k+1-i\) 个空段归并:\(\binom{k+1}{i}\)。最后我们任意顺序切他们中间的 \(k\) 个空当:\(k!\)

    刻画满足要求的形态:

    1. 段内为一个合法的拓扑序
    2. 短间不存在环:段间构成一张 DAG。

    第一个问题乱做即可,第二个问题可以使用经典 DAG 计数技巧,这个做卷积的时候涉及到很多麻烦的东西,只能枚举子集。

    然后记录个数的 \((+,\times)\) 可以利用拉差差系数做出来。

子结构分步拼接:

思路:
考虑图形的子结构,并且找到足够简单的组成图形的元素。得到这部分的集合幂级数后,逐步把子结构拼成最终的形态

树/森林生成子图计数

如果会做树的,森林直接 exp 就好了。

树的最简子结构是单个的点,没有比这个更简单的了。

\(p_u\) 表示当前树内所有点编号不超过 \(u\) 的集合幂级数。

每次相当于向 \(u\) 连边,可以先让 \(q_{u-1}=p_{u-1} \times w(u,*)\) 然后 \(p_u=exp(q_{u-1}) \cup \{u\}\)

这个复杂度是很好的,因为我们每次只利用了前 \(i\) 个点的信息,所以复杂度为 \(O(2^{i} \times i^2)=O(2^{n} n^2)\)

如果你把最简子结构换成边,那么中间的部分可以直接 exp 而不需要变换,但是这样复杂度是 \(O(2^n n^3)\) 的。

仙人掌生成子图计数

这个反着考虑就没有意义了,因为联通图并不能由仙人掌组合而成。

因此我们考虑正着做,那么正着做我们需要的是找到子结构。

仙人掌缩点后是一颗园方树,每一个环(包括二元环)对应一个方点,非叶子原点连接若干个方点。

方点的编号是未定义的,考虑限制整张图非叶子原点的编号 \(\le u\),称此时联通仙人掌的集合幂级数为 \(p_u\)

一开始的时候,不能有非叶子原点,因此只能是一个环。环的集合幂级数可以简单 dp 得出。

考虑 \(p_{u-1} \rightarrow p_{u}\),增加了 \(u\) 作为非叶子原点的部分,那么他一定连接若干个方点对应的仙人掌,这些仙人掌的贡献形式为 “无序不交组合”,我们直接做 \(\operatorname{exp}\) 即可得到,注意和点双的做法一样,需要合并的仙人掌包含 \(u\)

练习:

  • CTT2020 D4T1 杏仁

    如果没有限制,显然最简子结构是一条链。

    然后有限制相当于现做一个子集卷积,不过好在只求并为全集的,可以快速解决。

双联通限制:

思路:
处理这个的常用手段是 点/边双联通-联通 变换,名字很炫酷,实际也很炫酷。
尝试反过来解决问题:我们把最简子结构视为"双连通图",最终的结构视为连通图。
也就是说:假设我们已经知道了 \(f(x)\) 为双联通图的集合幂级数,同时已知每个集合的权值为 \(h(x)\),我们能否求出联通子图所对应的权值和。
如果能解决这个问题,且解决过程可逆,那联通子图权值和是可做问题,进而可以解出 \(f(x)\)

边双联通生成子图计数

\(p_u(S)\) 表示割边两端点的编号均不超过 \(u\) 的联通子图权值和。

那么对于 \(p_0(S)=f_S \times h_S\),因为不能有割边。而 \(p_n(S)\) 则相当于联通子图权值和,因为割边不受限制。

需要解决从 \(p_{u-1}\) 推向 \(p_u\)

新增的割边端点必定是 \(u\),考虑新增的部分,去掉所有 \(u\) 链接的割边后,一定分成若干联通块:\(P,T_1,T_2,T_3,……,T_k\),其中 \(u \in P,u \not \in T_i,P \cap T_i=\emptyset\),这些联通块贡献的形式是“无序组合”,尝试用 \(\operatorname{exp}\) 来描述。注意 \(P\) 是特别的,\(T\) 是类似的。\(T\) 需要和 \(P\) 连边,因此还需要刻画贡献。

\(q_{u-1}(S)=[u \not\in S]p_{u-1}(S) \times w(u,S)\),其中 \(w(u,S)\) 表示 \(S\) 中编号比 \(u\) 小的点有多少个,这些点与 \(u\) 相连将会成为链接联通块之间的割边。

则有递推式:

\[p_{u}=p_{u-1} *\operatorname{exp}(q_{u-1}) \]

注意这里面的函数都有定义域,如果不在域内不需要考虑。比如 \(p_u(S)=p_{u-1}(S),u \not \in S\)

我们需要考察这个做法是否可逆,也就是说,现在解决了如何从 \(p_0\) 推向 \(p_n\),接下来需要解决 \(p_n\) 推向 \(p_0\)

转移方程为 \(p_{u}=p_{u-1} * \operatorname{exp}(q_{u-1})\),知道 \(p_{u-1}\),我们还需要知道 \(p_u\)\(\operatorname{exp}(q_{u-1})\),而 \(q_{u-1}(S)=[u \not\in S]p_{u-1}(S) \times w(u,S)\)\(S,u \in S\) 无关,可以改写为 \(q_{u-1}(S)=[u \not\in S]p_{u}(S) \times w(u,S)\)

因此有:

\[p_{u-1}=p_{u} * \frac{1}{\operatorname{exp}(q_{u-1})} \]

\[p_{u-1}=p_u * \operatorname{exp}(-q_{u-1}) \]

只需要做一次 \(\operatorname{exp}\),再做一次子集卷积即可。注意定义域的问题,我们只取 \(p_{u},p_{u-1}\)\(u \in S\) 的部分,\(q_{u-1}\)\(u \not \in S\) 的部分做变换。

如果我们设 \(h_s=1\),则最终得到 \(p_0\) 就是答案。

点双联通分量生成子图计数

沿用上面的思路,仍然设 \(p_u\)

考虑增加一个点之后的增加的部分是什么。

去掉割点后,依然是剩下一些联通块,且仍然是以“不交无序组合”的形式贡献。

这次我们不需要再处理连边,而是直接使用包含割点的联通块信息,因此这些联通块实际形态应该是:全部包含 \(u\),去掉 \(u\) 之后的部分两两无交。

因此我们把所有包含 \(u\)\(S\) 先去掉 \(u\),再做 \(\operatorname{exp}\),然后再把得到的部分加上 \(u\) 替换当前的值。

逆过程是相对箱单的:把所有 \(S,u \in S\) 先去掉 \(u\),然后求 \(\operatorname{ln}\) 分解后再替换当前的值即可。

一个常数优化是,每次我们不从新扣出来包含 \(u\) 的然后再加上 \(u\),这需要做两次 FWT,太慢了。

我们直接使用 \(FWT\) 上的差分算出来,把上面这部分的 FWT 值挂在 \(S,u \in S\) 上面,做 \(\operatorname{ln}\),然后再加回来就好了。

常数优化的参考实现
for(int u=1;u<(1<<n);u<<=1)
	{
		for(int p=0;p<(1<<n);p+=u*2)
		{
			for(int i=p;i<p+u;i++)
			{
				static int a[N+1];
				for(int j=1;j<=n;j++) Add(f[j][i+u],mod-f[j][i]),a[j-1]=f[j][i+u];
				Exp(a);
				for(int j=1;j<=n;j++) f[j][i+u]=a[j-1],Add(f[j][i+u],f[j][i]);
			}
		}
	}
posted @ 2025-05-20 22:08  Richard_whr  阅读(122)  评论(2)    收藏  举报