【9*】集合幂级数学习笔记

news/2026/1/17 9:56:53/文章来源:https://www.cnblogs.com/w9095/p/19495238

前言

如果你不会集合幂级数,那你就进不了省队。

骗你的,会了也不一定能进省队。

长文警告:本文一共 \(1467\) 行,请合理安排阅读时间。

此类知识点大纲中并未涉及,所以【9】是我自己的估计,后带星号表示估计,仅供参考。

基础概念

卷积:设 \(a,b\) 是两个序列,则这两个序列对应的卷积得到的序列 \(c\) 定义为 \(c_k=\sum_{i+j=k}a_ib_j\)

形式幂级数\(A(x)=\sum_{i=0}^{\infty}a_ix^i\) 称作关于 \(x\)形式幂级数

集合幂级数:形式幂级数 \(F=\sum a_{n}x^n\) 描述了一个函数,类似的,我们对集合定义一个形式幂级数(即指数为一个集合) \(F=\sum a_S x^S\),我们将其称之为集合幂级数

一个常见的误区是把 \(x^S\) 理解成幂,但实际上它只是没有意义的占位符。

在存储时我们通常把 \(S\) 存储为其状态压缩串,并且和多项式一样,以数组下标 \(i\)\(x^i\),只存储系数。。

集合幂级数运算

以下记集合幂级数(或多项式) \(f\)\(x^S\) 项的系数为 \(f_S\)

集合幂级数加法\(\sum f_Sx^S+\sum g_Sx^S=\sum(f_S+g_S)x^S\)

集合幂级数乘法:集合幂级数乘法是一个自己定义的运算,需要满足交换律结合律集合幂级数乘法一般记作 \(\times\)

\(x^S\) 项与 \(x^\varnothing\) 项结合得到 \(x^S\) 项,此时允许我们把 \(cx^\varnothing\) 简记为 \(c\)

或卷积

\[f_Sx^Sg_Tx^T=f_Sg_Tx^{S\cup T} \]

上式是一个合法的乘法定义。集合幂级数 \(f\) 莫比乌斯变换结果是一个集合幂级数 \(\text{FMT}(f)\),满足 \(\text{FMT}(f)_S=\sum_{T\subseteq S}f_S\)。若在此乘法下 \(h=f\times g\),这个式子与如下式子等价。

\[\text{FMT}(h)_S=\text{FMT}(f)_S\text{FMT}(g)_S \]


\(h=f\times g\)\(c_{S}x^S=\sum_{X\cup Y=S}f_Xg_Yx^{X\cup Y}\),则 \(\text{FMT}(h)_S=\sum_{X\cup Y\subseteq S}f_Xg_Y\)

\[\begin{aligned} \text{FMT}(f)_S\text{FMT}(g)_S&=\sum_{X\subseteq S}f_X\sum_{Y\subseteq S}g_Y\\ &=\sum_{X\subseteq S}\sum_{Y\subseteq S}f_Xg_Y\\ &=\sum_{{(X\cup Y)}\subseteq S}f_Xg_Y\\ &=\text{FMT}(h)_{S} \end{aligned}\]


集合幂级数 \(f\)莫比乌斯逆变换定义为 \(\text{IFMT}(\text{FMT}(f))=f\)

从每一项的系数看,注意到 \(\text{FMT}\) 本质上是高维前缀和,我们只需要逐位对某一位满足偏序的元素做前缀和即可。正确性可以在三维空间中想象先逐行前缀和,再逐列前缀和,最后再逐层前缀和,每个点的答案恰好是其左下角的子长方体的答案,更高维度同理。

因此,\(\text{IFMT}\) 即为高维差分,我们只需要逐位对某一位满足偏序的元素做差分即可。

于是,求 \(h=f\times g\) 时,我们可以通过先求出 \(\text{FMT}(f),\text{FMT}(g)\),直接对位相乘得到 \(\text{FMT}(h)\),再做一遍 \(\text{IFMT}\) 就可以求出 \(h\)

写代码的时候,\(\text{FMT}\)\(\text{IFMT}\) 仅有对满足偏序的元素加(前缀和)或者减(差分)的区别,因此可以写到一起,用一个 \(fl\) 变量来控制,若 \(fl=1\) 则为 \(\text{FMT}\),若 \(fl=-1\) 则为 \(\text{IFMT}\)。记 \(n=2^k\) 为序列长度,时间复杂度 \(O(k2^k)\)

void fmt_or(int f[],int fl)
{for(int i=0;i<k;i++)for(int j=0;j<n;j++)if(j&(1<<i))f[j]=(1ll*f[j]+f[j^(1<<i)]*fl+mod)%mod;
}

与卷积

\[f_Sx^Sg_Tx^T=f_Sg_Tx^{S\cap T} \]

与卷积和或卷积同理,定义 \(\text{FMT}(f)_S=\sum_{S\subseteq T}f_Sx^S\) 后同样展开有 \(\text{FMT}(h)_S=\text{FMT}(f)_S\text{FMT}(g)_S\),同样可以 \(\text{IFMT}\) 回去求出 \(h\)。还是高位前缀和和高位差分,唯一的区别在于每一维的偏序关系反转了。

void fmt_and(int f[],int fl)
{for(int i=0;i<k;i++)for(int j=0;j<n;j++)if(j&(1<<i))f[j^(1<<i)]=(1ll*f[j^(1<<i)]+f[j]*fl+mod)%mod;
}

实际上,与卷积与或卷积本质相同,只不过是把或卷积所有集合去取了个反。

FMT 本质

之前我们认为集合幂级数中 \(x^S\) 是没有意义的,现在就让我们赋予其意义:\(x^S=\prod_{i\in S}x_i\)。因此,原式就变为了一个 \(n\) 元的 \(2^n\) 项的多项式。

我们不妨把集合看作一个向量 \((x_1,x_2\dots x_n)\),则 \(f(S)\) 相当于我们对每一个 \(x_i\) 都传入了一个值求出来的多项式的结果。因此,\(\text{FMT}\) 变换相当于求出了集合幂级数在此意义下对应的多项式的点值,因此可以对点相乘。因此,\(\text{FMT}\) 本质上是一个快速求值的过程。

进一步考虑 \(x_i\) 的取值。以或卷积为例,只考虑 \(x^S\) 项之间的运算,忽略系数,带入或卷积时的定义式。

\[x^Sx^T=x^{S\cup T} \]

\[\prod_{i\in S,i\notin T}x^i\prod_{j\in T,i\notin S}x^j\prod_{k\in S,i\in T}(x^k)^2=\prod_{i\in S\cup T}x^i \]

因此有 \((x^i)^2=x^i\),解得 \(x_i=0\)\(x_i=1\)。因此,带入 \(S\) 求点值时,我们不妨令满足 \(i\in S\)\(x_i=1\),满足 \(i\notin S\)\(x_i=0\)。经检验,求出的结果满足 \(\text{FMT}\) 的条件。

异或卷积

\[f_Sx^Sg_Tx^T=f_Sg_Tx^{S\oplus T} \]

集合幂级数 \(f\) 沃尔什变换结果是一个集合幂级数 \(\text{FWT}(f)\),满足 \(\text{FWT}(f)_S=\sum_{T\subseteq U}(-1)^{|T\cap S|}f_T\)。若在此乘法下 \(h=f\times g\),这个式子与如下式子等价。

\[\text{FWT}(h)_S=\text{FWT}(f)_S\text{FWT}(g)_S \]


\(h=f\times g\)\(c_{S}x^S=\sum_{X\oplus Y=S}f_Xg_Yx^{X\oplus Y}\),则 \(\text{FWT}(h)_S=\sum_{X,Y\subseteq U}(-1)^{|(X\oplus Y)\cap S|}f_Xg_Y\)

\(\bmod 2\) 意义下,\(|X\cap S|+|Y\cap S|\) 等价于 \(|(X\oplus Y)\cap S|\)。证明的话考虑每一位,\(S\) 中为 \(0\) 的位,两式都没有贡献。\(S\) 中为 \(1\) 的位,如果 \(X,Y\) 这两位值相同,左式会被模 \(2\) 消去,右式会被异或消去。如果 \(X,Y\) 这两位值不同,两式都有 \(1\) 的贡献。因此结论成立。

由于 \((-1)^2=1\),所以 \((-1)^{x}=(-1)^{x\bmod2}\)

\[\begin{aligned} \text{FWT}(f)_S\text{FWT}(g)_S&=\sum_{X\subseteq U}(-1)^{|X\cap S|}f_X\sum_{Y\subseteq U}(-1)^{|Y\cap S|}f_Y\\ &=\sum_{X\subseteq U}\sum_{Y\subseteq U}(-1)^{|X\cap S|+|Y\cap S|}f_X\\ &=\sum_{X\subseteq U}\sum_{Y\subseteq U}(-1)^{(|X\cap S|+|Y\cap S|)\bmod2}f_Xg_Y\\ &=\sum_{X,Y\subseteq U}(-1)^{|(X\oplus Y)\cap S|}f_Xg_Y\\ &=\text{FWT}(h)_S \end{aligned}\]


考虑怎么求出 \(\text{FWT}\) 后每一项的系数。考虑按二进制位分治,假设现在考虑到第 \(k\) 位(下标从 \(0\) 开始),前面的位已经考虑了。对于每一个第 \(k\) 位我们按照 \(2^{k+1}\) 为一组,每一个位置只考虑组内的贡献,则只需要考虑从上一位的 \(2^k\) 转移过来。

考虑有值的位置 \(y\),其需要合并的组中与这个数低位相同但高位没有值的位置为 \(x=y-2^{k}\)。考虑低位相同是因为可以直接将 \(x\) 组中的低位贡献继承。由于加两次 \(2^k\) 会导致第 \(k\) 变回 \(0\),发现对于所有这样的 \(x,y\),都有 \(x\) 所在的组第 \(k\) 位均为 \(0\)\(y\) 所在的组第 \(k\) 位均为 \(1\)

我们考虑合并时最高位的影响,对于 \(y\),与 \(x\) 组中的数合并时,最高位为 \(0\),取交集集合大小不变,直接继承;与 \(y\) 组中的数合并时,取交集最高位会多 \(1\),集合大小加 \(1\),乘以一次 \(-1\),继承 \(-y\),于是有 \(y'=x-y\)。同理,对于 \(x\),无论合并 \(x\) 还是 \(y\) 中的数交集大小都不变,于是有 \(x'=x+y\)

集合幂级数 \(f\)沃尔什逆变换定义为 \(\text{IFWT}(\text{FWT}(f))=f\)

于是,求 \(h=f\times g\) 时,我们可以通过先求出 \(\text{FWT}(f),\text{FWT}(g)\),直接对位相乘得到 \(\text{FWT}(h)\),再做一遍 \(\text{IFWT}\) 就可以求出 \(h\)

考虑如何求 \(\text{IFWT}\)。我们把 \(\text{FWT}\) 的过程逆过来,每次是根据两数和与差求出原来的两个数,解方程得 \(x=\frac{x'+y'}{2},y=\frac{x'-y'}{2}\),一直做下去即可。

这样单独写一个比较麻烦,但注意到 \(\text{FWT}\) 其实从高位到低位做这个过程也是对的,计算式完全一样。因此,\(\text{IFWT}\) 其实可以写得和 \(\text{FWT}\) 完全一样,只是每一步两个数额外除以一个 \(2\)。假设总共做了 \(k\) 轮,把这个除以 \(2\) 提出来,\(\text{IFWT}\) 就只需要做一遍 \(\text{FWT}\),然后对每个数除以 \(2^k\)

代码中,若 \(fl=0\) 则为 \(\text{FWT}\),若 \(fl=1\) 则为 \(\text{IFWT}\)。记 \(n=2^k\) 为序列长度,时间复杂度 \(O(k2^k)\)

void fmt_xor(int f[],int fl)
{for(int i=0;i<k;i++)for(int j=0;j<n;j++)if(j&(1<<i)){int x=f[j^(1<<i)],y=f[j];f[j^(1<<i)]=(x+y)%mod;f[j]=(x-y+mod)%mod;}if(fl){int inv=power(power(2,mod-2),k);for(int i=0;i<n;i++)f[i]=1ll*f[i]*inv%mod;}
}

FWT 本质

\(\text{FMT}\) 的本质,我们依旧通过定义 \(x^S=\prod_{i\in S}x_i\)\(f(S)\) 相当于我们对每一个 \(x_i\) 都传入了一个值求出来的多项式的结果来确定 \(\text{FWT}\) 的结果是一个点值,可以对点相乘。因此,\(\text{FWT}\) 本质上是一个快速求值的过程。

推导 \(x_i\) 的取值范围时有些许不同。只考虑 \(x^S\) 项之间的运算,忽略系数,带入或卷积时的定义式。

\[x^Sx^T=x^{S\oplus T} \]

\[\prod_{i\in S,i\notin T}x^i\prod_{j\in T,i\notin S}x^j\prod_{k\in S,i\in T}(x^k)^2=\prod_{i\in S\oplus T}x^i \]

因此有 \((x^i)^2=1\),解得 \(x_i=-1\)\(x_i=1\)。因此,带入 \(S\) 求点值时,我们不妨令满足 \(i\in S\)\(x_i=-1\),满足 \(i\notin S\)\(x_i=1\)。经检验,求出的结果满足 \(\text{FWT}\) 的条件。

子集卷积

若满足 \(S\cap T=\varnothing\),则有 \(f_Sx^Sg_Tx^T=f_Sg_Tx^{S\cup T}\)。这等价于把一个集合划分成两个互不相交的子集进行转移,是一种集合幂级数乘法,称为子集卷积

还是转换成点值,之后对点相乘。

\(S\cap T=\varnothing\) 等价于 \(|S|+|T|=|S\cup T|\),因此我们考虑记录集合的大小。考虑加入辅助元 \(z^k\),包含 \(z^k\) 的项表示这一项的集合大小为 \(k\)。初始时,我们把 \(a_i\) 存到 \(x^iz^{\text{popcount}(i)}\) 项,使用二维数组存储。

记录了集合大小后,我们就可以直接将包含 \(z^i\) 的项和 \(z^j\) 的项合并到包含 \(z^{i+j}\) 的项。然后考虑 \(x\),此时我们不需要再考虑 \(z\),就可以直接套用或卷积了。

在实现时,我们把 \(z^k\) 项相同的项排到一起,看作一个集合幂级数做 \(\text{FMT}\)。对 \(f,g\) 分别做,然后按照 \(z\) 的合并规则合并到对应的 \(z\) 的项,并按照 \(x\) 的合并规则对点相乘合并到答案 \(h\)。最后,再对 \(h\) 的每一个 \(z\) 项对应的集合幂级数求一遍 \(\text{IFMT}\),在 \(h\)\(x^iz^{\text{popcount}(i)}\) 的系数处就可以查询出子集卷积后集合 \(i\) 的结果。

\(n=2^k\) 为序列长度,时间复杂度 \(O(k^22^k)\)

for(int i=0;i<n;i++)f[__builtin_popcount(i)][i]=a[i];
for(int i=0;i<n;i++)g[__builtin_popcount(i)][i]=b[i];
for(int i=0;i<=k;i++)fmt_or(f[i],1),fmt_or(g[i],1);
for(int i=0;i<=k;i++)for(int j=0;j<=i;j++)for(int k=0;k<n;k++)h[i][k]=(h[i][k]+1ll*f[j][k]*g[i-j][k])%mod;
for(int i=0;i<=k;i++)fmt_or(h[i],-1);

集合幂级数复合

多项式函数与集合幂级数复合 \(F(f)\) 的结果是一个集合幂级数,即将集合幂级数带入函数 \(F\) 后求出的集合幂级数。

由于 \(\text{FMT}\) 可以看作点值,因此运算后的集合幂级数的点值可以看作把 \(f\) 的点值带入 \(F\)。也就是这个式子。

\[\text{FMT}(F(f)_S)=F(\text{FMT}(f)_S) \]

这启示我们,对于集合幂级数复合,可以先 \(\text{FMT}\) 求出点值,再对点值做函数复合,最后使用 \(\text{IFMT}\) 求出复合后的集合幂级数。

集合幂级数 exp

\(f\) 是集合幂级数,集合幂级数 \(g\) 满足 \(g=e^{f}\),则称 \(g\)\(f\)集合幂级数 exp\(e^{f}\) 可以泰勒展开得到如下式子。

\[e^{f}=\sum_{n\ge 0}\frac{f^n}{n!} \]

考虑求出点值,然后对点值进行复合。由于无论是或卷积还是异或卷积,点值都是整数,\(e^x\) 在 OI 中不好表示,于是我们考虑子集卷积下的集合幂级数 exp。

\(\text{FMT}\) 求出带入 \(S\) 的点值,由于占位元 \(z\) 的影响,我们得到的点值是一个多项式,因此我们对集合幂级数做 \(\exp\),等价于对点值的多项式做 \(\exp\),最后再 \(\text{IFMT}\) 回去。

注意 \(\text{FMT}\)\(\text{IFMT}\) 已经有 \(O(k^22^k)\) 的时间复杂度,因此对于长度为 \(k\) 的点值,我们可以使用简洁且常数小的 \(O(n^2)\) 暴力递推多项式 exp,只有 \(O(k^22^k)\) 的总复杂度。

具体的,对于多项式 \(G(x)=e^{F(x)}\),我们可以进行如下变换。

\[G'(x)=(e^{F(x)})' \]

\[G'(x)=e^{F(x)}F'(x) \]

\[G'(x)=G(x)F'(x) \]

\(f_i\) 表示多项式 \(F(x)\) 的第 \(i\) 项,\(g_i\) 表示多项式 \(G(x)\) 的第 \(i\) 项,两边全部展开成每一项。

\[\sum_{i=0}^{n-1}g_{i+1}(i+1)x^i=\sum_{i=0}^ng_ix^i\sum_{j=0}^{n-1}f_{j+1}(j+1)x^j \]

考虑左边第 \(k\gt 0\) 项的系数。

\[g_{k+1}(k+1)=\sum_{i=0}^{k} f_{i+1}(i+1)g_{k-i} \]

换元,令 \(k+1\to k,i+1\to i\),将左式的常数除过去,再令 \(k-i\to i\),得到递推式。

\[g_{k}=\frac{1}{k}\sum_{i=0}^{k-1} f_{k-i}ig_{i} \]

递推式还需要一个初始值,集合幂级数 exp 的定义中要求 \(f_{\varnothing}=0\),带入 \(\text{FMT}\) 的变换过程有每一个点值对应的多项式第 \(0\) 项均为 \(0\),满足多项式 exp 的要求。多项式 exp 的展开式中常数项为 \(\frac{x^0}{0!}\),可以直接算出 \(g_0=1\)

inline void exp(int f[])
{for(int i=1;i<=k;i++)g[i]=0;g[0]=1;for(int i=0;i<=k;i++)f[i]=1ll*f[i]*i%mod;for(int i=1;i<=k;i++){for(int j=0;j<i;j++)g[i]=(g[i]+1ll*g[j]*f[i-j])%mod;g[i]=1ll*g[i]*inv[i]%mod;}for(int i=0;i<=k;i++)f[i]=g[i];
}void fmt_exp()
{for(int i=1;i<=k;i++)inv[i]=power(i,mod-2);for(int i=0;i<=k;i++)fmt_or(f[i],1);for(int i=0;i<n;i++){for(int j=0;j<=k;j++)a[i][j]=f[j][i];exp(a[i]);}for(int i=0;i<=k;i++)for(int j=0;j<n;j++)f[i][j]=a[j][i];for(int i=0;i<=k;i++)fmt_or(f[i],-1);
}

补充一下,关于为什么多项式 exp 要求多项式常数项为 \(0\):因为多项式 exp 的展开式中常数项恒为 \(1\),如果常数项不为 \(0\) 则带入点值 \(0\)\(\exp\) 的结果是 \(e\) 而不是 \(1\),所以不能。

集合幂级数 ln

\(f\) 是集合幂级数,集合幂级数 \(g\) 满足 \(g=\ln f\),则称 \(g\)\(f\)集合幂级数 ln\(\ln f\) 满足 \(e^{\ln f}=f\)

同 exp,考虑子集卷积下的集合幂级数 ln,等价于求多项式 ln。

由于 \(\ln\) 定义为 \(\exp\) 的逆运算,所以 \(\ln\) 要求 \(f_0=1\),而 \(g_0=0\)。集合幂级数 ln 的定义中要求 \(f_{\varnothing}=1\),带入 \(\text{FMT}\) 的变换过程有每一个点值对应的多项式第 \(1\) 项均为 \(1\),满足多项式 ln 的要求。

多项式 ln 一样可以 \(O(n^2)\) 递推,考虑对 \(G(x)=\ln F(x)\) 如下变换。

\[G'(x)=(\ln F(x))' \]

\[G'(x)=\frac{1}{F(x)}F'(x) \]

\[G'(x)F(x)=F'(x) \]

\(f_i\) 表示多项式 \(F(x)\) 的第 \(i\) 项,\(g_i\) 表示多项式 \(G(x)\) 的第 \(i\) 项,两边全部展开成每一项。

\[\sum_{i=0}^{n-1}g_{i+1}(i+1)x^i\sum_{j=0}^nf_jx^j=\sum_{i=0}^{n-1}f_{i+1}(i+1)x^i \]

考虑右边第 \(k\gt 0\) 项的系数。

\[\sum_{i=0}^kg_{i+1}(i+1)f_{k-i}=f_{k+1}(k+1) \]

换元,令 \(k+1\to k,i+1\to i\),经过一番变形得到递推式

\[g_kkf_0+\sum_{i=1}^{k-1}g_iif_{k-i}=f_kk \]

\[g_k=f_k-\frac{1}{k}\sum_{i=1}^{k-1}g_iif_{k-i} \]

void ln(int f[])
{for(int i=0;i<=k;i++)g[i]=0;for(int i=1;i<=k;i++){for(int j=1;j<=i-1;j++)g[i]=(g[i]+1ll*g[j]*j%mod*f[i-j]%mod)%mod;g[i]=(f[i]-1ll*g[i]*inv[i]%mod+mod)%mod;}for(int i=0;i<=k;i++)f[i]=g[i];
}void fmt_ln()
{for(int i=1;i<=k;i++)inv[i]=power(i,mod-2);for(int i=0;i<=k;i++)fmt_or(f[i],1);for(int i=0;i<n;i++){for(int j=0;j<=k;j++)a[i][j]=f[j][i];ln(a[i]);}for(int i=0;i<=k;i++)for(int j=0;j<n;j++)f[i][j]=a[j][i];for(int i=0;i<=k;i++)fmt_or(f[i],-1);
}

集合幂级数逆

\(f\) 是集合幂级数,集合幂级数 \(g\) 满足 \(g=\frac{1}{f}\),则称 \(g\)\(f\)集合幂级数逆

同 exp 和 ln,考虑子集卷积下的集合幂级数逆,等价于求多项式乘法逆。

考虑常数项,则 \(g_0\) 必然为 \(f_0\) 的逆元;若 \(f_0\) 无逆元,即 \(f_0=0\),自然也没办法求多项式乘法逆。集合幂级数逆的定义中要求 \(f_{\varnothing}\ne0\),带入 \(\text{FMT}\) 的变换过程有每一个点值对应的多项式第 \(1\) 项均为 \(f_0\),满足多项式乘法逆的要求。

多项式乘法逆一样可以 \(O(n^2)\) 递推,考虑对 \(G(x)=\frac{1}{F(x)}\) 等价于 \(G(x)F(x)=1\),令 \(f_i\) 表示多项式 \(F(x)\) 的第 \(i\) 项,\(g_i\) 表示多项式 \(G(x)\) 的第 \(i\) 项,两边全部展开成每一项。

\[\sum_{i=0}^ng_ix^i\sum_{j=0}^nf_jx^j=1 \]

考虑左边第 \(k\gt 0\) 项的系数,此时显然为 \(0\),因为右边只有一个常数 \(1\)。再令 \(k-i\to i\) 得到递推式。

\[\sum_{i=0}^kf_ig_{k-i}=0 \]

\[f_0g_{k}+\sum_{i=1}^{k}f_ig_{k-i}=0 \]

\[g_{k}=-\frac{1}{f_0}\sum_{i=1}^{k}f_ig_{k-i}=-g_0\sum_{i=0}^{k-1}f_{k-i}g_i \]

void ln(int f[])
{for(int i=0;i<=n;i++)g[i]=0;for(int i=1;i<=n;i++){for(int j=1;j<=i-1;j++)g[i]=(g[i]+1ll*g[j]*j%mod*f[i-j])%mod;g[i]=(f[i]-1ll*g[i]*iv[i]%mod+mod)%mod;}for(int i=0;i<=n;i++)f[i]=g[i];
}void fmt_inv()
{for(int i=0;i<=k;i++)fmt_or(f[i],1);for(int i=0;i<n;i++){for(int j=0;j<=k;j++)a[i][j]=f[j][i];inv(a[i]);}for(int i=0;i<=k;i++)for(int j=0;j<n;j++)f[i][j]=a[j][i];for(int i=0;i<=k;i++)fmt_or(f[i],-1);
}

集合幂级数 k-exp

\(f\) 是集合幂级数,集合幂级数 \(g\) 满足如下式子,则称 \(g\)\(f\)集合幂级数 k-exp

\[g=\sum_{n=0}^k\frac{f^n}{n!} \]

你发现 \(\text{k-exp}\) 其实就是 \(e^{f}\) 泰勒展开的前 \(k\) 项。常数项 \(g_0=\sum_{i=0}^k\frac{f_0^i}{i!}\),因此也没有 \(\exp\) 的常数项要求。

还是转化为多项式 k-exp。对 $$G(x)=\sum_{i=0}k\frac{F(x)i}{i!}$$ 两边求导。

\[G'(x)=\sum_{i=0}^k(\frac{F(x)^i}{i!})' \]

\[G'(x)=\sum_{i=1}^k\frac{iF(x)^{i-1}F'(x)}{i!} \]

\[G'(x)=\sum_{i=1}^k\frac{F(x)^{i-1}F'(x)}{(i-1)!} \]

\[G'(x)=F'(x)\sum_{i=0}^{k-1}\frac{F(x)^i}{i!} \]

注意到 \(G(x)-\frac{F(x)^k}{k!}=\sum_{i=0}^{k-1}\frac{F(x)^i}{i!}\),设 \(H(x)=\frac{F(x)^k}{k!}\),则原式可化为如下式子。

\[G'(x)=F'(x)(G(x)-H(x)) \]

\(f_i\) 表示多项式 \(F(x)\) 的第 \(i\) 项,\(g_i\) 表示多项式 \(G(x)\) 的第 \(i\) 项,\(h_i\) 表示多项式 \(G(x)\) 的第 \(i\) 项,两边全部展开成每一项。

\[\sum_{i=0}^{n-1}g_{i+1}(i+1)x^i=\sum_{i=0}^{n-1}f_{i+1}(i+1)x^i\sum_{j=0}^n (g_j-h_j) \]

考虑左边第 \(k\gt 0\) 项的系数。

\[g_{k+1}(k+1)=\sum_{i=0}^{k}f_{i+1}(i+1)(g_{k-i}-h_{k-i}) \]

换元,令 \(k+1\to k,i+1\to i\),常数项已知,将 \(k\) 除到右边得到递推式。

\[g_k=\frac{1}{k}\sum_{i=1}^{k}f_ii(g_{k-i}-h_{k-i}) \]

接下来考虑求 \(H(x)\)。除以 \(k!\) 可以算完 \(F(x)^k\) 后再除,因此问题转化为了求 \(F(x)^k\)。一个直观的想法是先求 \(\ln\),再对多项式乘以 \(k\),最后 \(\exp\) 回去。但 \(\ln\) 要求首项必须是 \(1\),因此找到从低到高第一个系数不为 \(0\) 的项,记作第 \(t\) 项,有如下式子。

\[F(x)^k=(\frac{F(x)}{f_tx^t})^kf_t^kx^{kt} \]

\(\frac{F(x)}{f_tx^t}\) 满足 \(\ln\) 首项必须是 \(1\) 的要求,除以 \(x^t\) 可以通过整体左移实现。同理,最后的乘以 \(x^{kt}\) 也可以通过整体右移实现。

void power(int f[],int p)
{int t=0,c=0,inv=0,pw=0;while(f[t]==0&&t<=k)t++;if(t==k+1)return;c=f[t],inv=power(c,mod-2),pw=power(c,p);for(int i=0;i<=k;i++)f[i]=1ll*f[i]*inv%mod;if(t)for(int i=0;i+t<=k;i++)f[i]=f[i+t],f[i+t]=0;ln(f);for(int i=0;i<=k;i++)f[i]=1ll*f[i]*p%mod;exp(f);if(1ll*p*t>=k+1)for(int i=0;i<=k;i++)f[i]=0;else {for(int i=k;i>=p*t;i--)f[i]=f[i-p*t],f[i-p*t]=0;for(int i=0;i<p*t;i++)f[i]=0;for(int i=p*t;i<=k;i++)f[i]=1ll*f[i]*pw%mod;}
}void kexp(int f[],int p)
{for(int i=0;i<=k;i++)h[i]=f[i];power(h,p);for(int i=0;i<=k;i++)h[i]=1ll*h[i]*jnv[p]%mod,g[i]=0;int pw=1;for(int i=0;i<=p;i++)g[0]=(g[0]+1ll*pw*jnv[i])%mod,pw=1ll*pw*f[0]%mod;for(int i=1;i<=k;i++){for(int j=0;j<i;j++)g[i]=(g[i]+1ll*(g[j]-h[j])*f[i-j]%mod*(i-j))%mod;g[i]=1ll*g[i]*inv[i]%mod;}for(int i=0;i<=k;i++)f[i]=g[i];
}void fmt_kexp(int p)
{jnv[0]=1;for(int i=1;i<=k;i++)inv[i]=power(i,mod-2),jnv[i]=1ll*jnv[i-1]*inv[i]%mod;for(int i=0;i<=k;i++)fmt_or(f[i],1);for(int i=0;i<n;i++){for(int j=0;j<=k;j++)a[i][j]=f[j][i];kexp(a[i],p);}for(int i=0;i<=k;i++)for(int j=0;j<n;j++)f[i][j]=a[j][i];for(int i=0;i<=k;i++)fmt_or(f[i],-1);
}

图论计数

组合意义

定义集合的划分为将集合划分为为若干个子集,使得这些子集之间没有交集,且并集为这个集合。

以下乘法均定义为子集卷积。集合幂级数 \(f,g\) 的乘法的组合意义为:将集合划分成两个互不相交且集合,一种划分的贡献为第一个子集的 \(f\) 与第二个子集的 \(g\) 之积,所有划分方案的和。

集合幂级数 \(f^n\) 的组合意义为:\((f^n)_S\) 表示将 \(S\) 划分成 \(n\) 个部分,每个部分间有标号。一种划分方案的权值为各部分 \(T\)\(f_T\) 之积,\((f^n)_S\) 为所有划分方案的权值和。

对于集合幂级数 \(f,g\),满足 \(g=\exp f\),也就是 \(f= \ln g\)。这一关系式的组合意义是:\(g_S\) 表示将 \(S\) 划分成若干个部分,每个部分间无标号。一种划分方案的权值为各部分 \(T\)\(f_T\) 之积,\((f^n)_S\) 为所有划分方案的权值和。证明的话考虑 \(\exp f=\sum_{n\ge 0}\frac{f^n}{n!}\),这里的除以 \(n!\) 就相当于去除标号的影响。

当权值 \(f_S\) 为集合 \(S\) 满足条件的方案数,且 \(f\) 之间的转移是子集关系时,\((f^n)_S\)\((\exp f)_S\) 便成为了集合 \(S\) 的满足条件的方案数。

作为逆运算,\(\ln\)\(\exp\) 有相似的组合意义,但一般用于先通过 \(\exp\) 将问题转化为一个方便求的问题,再通过 \(\ln\) 求出原问题。

同理,\(\text{k-exp}\) 的组合意义为,\(g_S\) 表示将 \(S\) 划分成 \(k\) 个部分,每个部分间无标号。一种划分方案的权值为各部分 \(T\)\(f_T\) 之积,\((f^n)_S\) 为所有划分方案的权值和。

连通性限制

定义集合 \(S\) 中的边为两点都在集合 \(S\) 中的边。生成子图为选择边的子集形成的图,并附加所有与选的边相连的点。

当题目中存在连通性限制,我们一般通过 \(\exp\)\(\ln\) 的组合意义将问题转化为 \(S\) 中的点与边选与不选或如何决策的问题,最后再 \(\ln\) 回去。例如下面母题,是处理连通性限制的示范。


给定一个简单无向图 \(G=(V,E)\),其中 \(|V|=n\)\(|E|=m\)。求有多少边集 \(E'\subseteq E\) 使得子图 \((V, E')\) 是连通图。答案对 \(998244353\) 取模。

\(1\leq n\leq 20,0 \leq m \leq \binom{n}{2}\)


考虑设集合幂级数 \(f\)\(f_S\) 表示 \(S\) 的生成子图中使 \(S\) 所有点连通的数量,则 \(g=\exp f\) 的组合意义为 \(S\) 由若干个连通图拼接起来的方案数,即 \(S\) 的任意生成子图。点是不能决策的,只有边可以决策选与不选。由于是若干个连通块拼起来,考虑每个点能到的点可以发现必然不会算重,因此 \(g_S\) 就是 \(S\) 中的边任意选的方案数。设 \(S\) 中有 \(e(S)\) 条边,则 \(g_S=2^{e(S)}\)\(\ln\)\(\exp\) 的逆运算,可以直接通过 \(\ln\) 求出 \(f\)。解决原问题只需要对 \(f\) 每一项的系数求和即可。

这题一方面启发我们,子图的连通生成子图是可以快速计算的,可以作为已知信息;另一方面,我们可以类比本题的过程,在连通性图论计数问题中去除连通性的限制,最后额外做一步 \(\ln\) 即可。

例题

例题 \(1\)

P4717 【模板】快速莫比乌斯 / 沃尔什变换 (FMT / FWT)

模板题,不多赘述。

#include <bits/stdc++.h>
using namespace std;
int n,k,a[200000],b[200000],c[200000],d[200000];
const int mod=998244353;
int power(int a,int p)
{int x=a,ans=1;while(p){if(p&1)ans=1ll*ans*x%mod;p>>=1;x=1ll*x*x%mod;}return ans;
}void fmt_or(int f[],int fl)
{for(int i=0;i<k;i++)for(int j=0;j<n;j++)if(j&(1<<i))f[j]=(1ll*f[j]+f[j^(1<<i)]*fl+mod)%mod;
}void fmt_and(int f[],int fl)
{for(int i=0;i<k;i++)for(int j=0;j<n;j++)if(j&(1<<i))f[j^(1<<i)]=(1ll*f[j^(1<<i)]+f[j]*fl+mod)%mod;
}void fmt_xor(int f[],int fl)
{for(int i=0;i<k;i++)for(int j=0;j<n;j++)if(j&(1<<i)){int x=f[j^(1<<i)],y=f[j];f[j^(1<<i)]=(x+y)%mod;f[j]=(x-y+mod)%mod;}if(fl){int inv=power(power(2,mod-2),k);for(int i=0;i<n;i++)f[i]=1ll*f[i]*inv%mod;}
}int main()
{scanf("%d",&k);n=(1<<k);for(int i=0;i<n;i++)scanf("%d",&a[i]),c[i]=a[i];for(int i=0;i<n;i++)scanf("%d",&b[i]),d[i]=b[i];fmt_or(a,1),fmt_or(b,1);for(int i=0;i<n;i++)a[i]=1ll*a[i]*b[i]%mod;fmt_or(a,-1);for(int i=0;i<n;i++)printf("%d ",a[i]),a[i]=c[i],b[i]=d[i];printf("\n");fmt_and(a,1),fmt_and(b,1);for(int i=0;i<n;i++)a[i]=1ll*a[i]*b[i]%mod;fmt_and(a,-1);for(int i=0;i<n;i++)printf("%d ",a[i]),a[i]=c[i],b[i]=d[i];printf("\n");fmt_xor(a,0),fmt_xor(b,0);for(int i=0;i<n;i++)a[i]=1ll*a[i]*b[i]%mod;fmt_xor(a,1);for(int i=0;i<n;i++)printf("%d ",a[i]);printf("\n");return 0;
}

例题 \(2\)

P6097 【模板】子集卷积

模板题,不多赘述。

#include <bits/stdc++.h>
using namespace std;
int n,k,a[2000000],b[2000000],f[21][1200000],g[21][1200000],h[21][1200000];
const int mod=1e9+9;
void fmt_or(int f[],int fl)
{for(int i=0;i<k;i++)for(int j=0;j<n;j++)if(j&(1<<i))f[j]=(1ll*f[j]+f[j^(1<<i)]*fl+mod)%mod;
}int main()
{scanf("%d",&k);n=(1<<k);for(int i=0;i<n;i++)scanf("%d",&a[i]),f[__builtin_popcount(i)][i]=a[i];for(int i=0;i<n;i++)scanf("%d",&b[i]),g[__builtin_popcount(i)][i]=b[i];for(int i=0;i<=k;i++)fmt_or(f[i],1),fmt_or(g[i],1);for(int i=0;i<=k;i++)for(int j=0;j<=i;j++)for(int k=0;k<n;k++)h[i][k]=(h[i][k]+1ll*f[j][k]*g[i-j][k])%mod;for(int i=0;i<=k;i++)fmt_or(h[i],-1);for(int i=0;i<n;i++)printf("%d ",h[__builtin_popcount(i)][i]);printf("\n");return 0;
}

例题 \(3\)

P12230 集合幂级数 exp

模板题,不多赘述。

#include <bits/stdc++.h>
using namespace std;
int n,k,f[22][2000000],a[2000000][22],g[22],inv[22];
const int mod=998244353;
int power(int a,int p)
{int x=a,ans=1;while(p){if(p&1)ans=1ll*ans*x%mod;p>>=1;x=1ll*x*x%mod;}return ans;
}void fmt_or(int f[],int fl)
{for(int i=0;i<k;i++)for(int j=0;j<n;j++)if(j&(1<<i))f[j]=(1ll*f[j]+f[j^(1<<i)]*fl+mod)%mod;
}void exp(int f[])
{for(int i=0;i<=k;i++)g[i]=0;g[0]=1;for(int i=1;i<=k;i++){for(int j=1;j<=i;j++)g[i]=(g[i]+1ll*f[j]*j%mod*g[i-j]%mod)%mod;g[i]=1ll*g[i]*inv[i]%mod;}for(int i=0;i<=k;i++)f[i]=g[i];
}int main()
{scanf("%d",&k);n=(1<<k);for(int i=0;i<n;i++)scanf("%d",&f[__builtin_popcount(i)][i]);for(int i=1;i<=k;i++)inv[i]=power(i,mod-2);for(int i=0;i<=k;i++)fmt_or(f[i],1);for(int i=0;i<n;i++){for(int j=0;j<=k;j++)a[i][j]=f[j][i];exp(a[i]);}for(int i=0;i<=k;i++)for(int j=0;j<n;j++)f[i][j]=a[j][i];for(int i=0;i<=k;i++)fmt_or(f[i],-1);for(int i=0;i<n;i++)printf("%d ",f[__builtin_popcount(i)][i]);printf("\n");return 0;
}

例题 \(4\)

P12231 集合幂级数 ln

模板题,不多赘述。

#include <bits/stdc++.h>
using namespace std;
int n,k,f[22][2000000],a[2000000][22],g[22],inv[22];
const int mod=998244353;
int power(int a,int p)
{int x=a,ans=1;while(p){if(p&1)ans=1ll*ans*x%mod;p>>=1;x=1ll*x*x%mod;}return ans;
}void fmt_or(int f[],int fl)
{for(int i=0;i<k;i++)for(int j=0;j<n;j++)if(j&(1<<i))f[j]=(1ll*f[j]+f[j^(1<<i)]*fl+mod)%mod;
}void ln(int f[])
{for(int i=0;i<=k;i++)g[i]=0;for(int i=1;i<=k;i++){for(int j=1;j<=i-1;j++)g[i]=(g[i]+1ll*g[j]*j%mod*f[i-j]%mod)%mod;g[i]=(f[i]-1ll*g[i]*inv[i]%mod+mod)%mod;}for(int i=0;i<=k;i++)f[i]=g[i];
}int main()
{scanf("%d",&k);n=(1<<k);for(int i=0;i<n;i++)scanf("%d",&f[__builtin_popcount(i)][i]);for(int i=1;i<=k;i++)inv[i]=power(i,mod-2);for(int i=0;i<=k;i++)fmt_or(f[i],1);for(int i=0;i<n;i++){for(int j=0;j<=k;j++)a[i][j]=f[j][i];ln(a[i]);}for(int i=0;i<=k;i++)for(int j=0;j<n;j++)f[i][j]=a[j][i];for(int i=0;i<=k;i++)fmt_or(f[i],-1);for(int i=0;i<n;i++)printf("%d ",f[__builtin_popcount(i)][i]);printf("\n");return 0;
}

例题 \(5\)

P12232 集合幂级数求逆

模板题,不多赘述。

#include <bits/stdc++.h>
using namespace std;
int n,k,f[22][2000000],a[2000000][22],g[22];
const int mod=998244353;
int power(int a,int p)
{int x=a,ans=1;while(p){if(p&1)ans=1ll*ans*x%mod;p>>=1;x=1ll*x*x%mod;}return ans;
}void fmt_or(int f[],int fl)
{for(int i=0;i<k;i++)for(int j=0;j<n;j++)if(j&(1<<i))f[j]=(1ll*f[j]+f[j^(1<<i)]*fl+mod)%mod;
}void inv(int f[])
{for(int i=0;i<=k;i++)g[i]=0;g[0]=power(f[0],mod-2);for(int i=1;i<=k;i++){for(int j=1;j<=i;j++)g[i]=(g[i]+1ll*f[j]*g[i-j]%mod)%mod;g[i]=(mod-1ll*g[i]*g[0]%mod+mod)%mod;}for(int i=0;i<=k;i++)f[i]=g[i];
}int main()
{scanf("%d",&k);n=(1<<k);for(int i=0;i<n;i++)scanf("%d",&f[__builtin_popcount(i)][i]);for(int i=0;i<=k;i++)fmt_or(f[i],1);for(int i=0;i<n;i++){for(int j=0;j<=k;j++)a[i][j]=f[j][i];inv(a[i]);}for(int i=0;i<=k;i++)for(int j=0;j<n;j++)f[i][j]=a[j][i];for(int i=0;i<=k;i++)fmt_or(f[i],-1);for(int i=0;i<n;i++)printf("%d ",f[__builtin_popcount(i)][i]);printf("\n");return 0;
}

例题 \(6\)

P3175 [HAOI2015] 按位或

考虑将期望转化为概率,考虑第一次出现的时刻,设 \(f_{i,S}\) 表示 \(i\) 秒后到状态 \(S\) 的概率,则到 \(S\) 的期望步数为 \(\sum_{i\gt0}i(f_{i,S}-f_{i-1,S})\)。而根据概率 DP 的经典模型有如下关系式,初始时 \(f_{0,\varnothing}=1\),其余为 \(0\)

\[f_{i,S}=\sum_{X\cup Y=S}f_{i-1,X}p_Y \]

这时或卷积的形式,我们写出 \(f_i,p\) 的集合幂级数,每一秒的转移等价于 \(f_i\) 乘以 \(p\)。又由于 \(f_0\) 只有 \(f_{0,\varnothing}=1\),故 \(f_i=p^i\)。带回答案的式子。

\[\sum_{i\gt0}i(p^i_{S}-p^{i-1}_{S}) \]

\(\text{DFT}\) 后,集合幂级数乘法每个点值独立,为 \(p^i_S\)。注意到 \(p^i_S\le 1\),因此要么 \(p^i_S\lt1\),答案式子是收敛的,拆括号合并同类项有 \(\sum_{i\ge0}-p^i_S\),于是套用等比数列求和公式得答案为 \(\frac{1}{p_S-1}\)。要么 \(p^i_S=1\),代入原式算出来等于 \(0\)

于是,我们先对 \(p\) 数组做一遍 \(\text{DFT}\),再对集合幂级数每个点值做如上操作,最后 \(\text{IDFT}\) 回去,\(p_U\) 即为答案。

#include <bits/stdc++.h>
using namespace std;
int n,k,fl=0;
long double p[2000000];
const long double eps=1e-9;
void fmt_or(long double f[],int fl)
{for(int i=0;i<k;i++)for(int j=0;j<n;j++)if(j&(1<<i))f[j]+=fl*f[j^(1<<i)];
}int main()
{scanf("%d",&k);n=(1<<k);for(int i=0;i<n;i++)scanf("%Lf",&p[i]);fmt_or(p,1);for(int i=0;i<n;i++){if(abs(1-p[i])<eps)p[i]=0;else p[i]=1/(p[i]-1);}fmt_or(p,-1);if(p[n-1]<eps)printf("INF\n");else printf("%.9Lf\n",p[n-1]);return 0;
}

例题 \(7\)

CF662C Binary Table

显然每行每列最多翻转一次。由于行数比较小,我们可以枚举每行是否被翻转,暴力修改后考虑每一列,如果 \(0\) 少于 \(1\) 就翻转,即取 \(0\)\(1\) 的数量的较小值。

考虑每一列的状态。记 \(a_S\) 表示初始时状态为 \(S\) 的列的数量,\(b_S\) 表示状态 \(S\)\(0\)\(1\) 的数量的较小值。枚举行的翻转状态 \(T\),考虑列的所有状态 \(S\),得到此时答案的表达式,记作 \(f_T\)

\[f_T=\sum_{S\subseteq U}a_Sb_{S\oplus T} \]

\(S\oplus T\to T\),则有如下式子。

\[f_{S\oplus T}=\sum_{S\subseteq U}a_Sb_T \]

可以转化为 \(f_{S}=\sum_{X\oplus Y=S}a_Xb_Y\),是异或卷积的形式,直接 \(\text{FWT}\) 就做完了。

#include <bits/stdc++.h>
using namespace std;
long long n,k,m,s[30][200000],f[2000000],g[2000000],mi=1e9;
void fmt_xor(long long f[],long long fl)
{for(int i=0;i<k;i++)for(int j=0;j<n;j++)if(j&(1<<i)){long long x=f[j^(1<<i)],y=f[j];f[j^(1<<i)]=(x+y);f[j]=(x-y);}if(fl)for(int i=0;i<n;i++)f[i]/=(1<<k);
}int main()
{scanf("%lld%lld",&n,&m);for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)scanf("%1lld",&s[i][j]);for(int i=1;i<=m;i++){int id=0;for(int j=1;j<=n;j++)id=(id<<1)|s[j][i];f[id]++;}k=n,n=(1<<k);for(int i=0;i<n;i++)g[i]=min(1ll*__builtin_popcountll(i),k-__builtin_popcountll(i));fmt_xor(f,0),fmt_xor(g,0);for(int i=0;i<n;i++)f[i]*=g[i];fmt_xor(f,1);for(int i=0;i<n;i++)mi=min(mi,f[i]);printf("%lld\n",mi);return 0;
}

例题 \(8\)

AT_abc288_g [ABC288G] 3^N Minesweeper

考虑 \(\text{FWT}\) 的本质,对于第 \(i\) 位为 \(1\) 的数 \(a_x,a_{x-2^i}\),我们乘上了一个转移矩阵。考虑 \(\text{IFWT}\) 的本质,对于第 \(i\) 位为 \(1\) 的数 \(a_x,a_{x-2^i}\),我们乘上了一个 \(\text{FWT}\) 的逆矩阵。

考虑扩展到三进制的情况:套用 \(\text{FWT}\) 的按位考虑,对于第 \(i\) 位为 \(2\) 的数 \(a_x,a_{x-3^i},a_{x-2\times3^i}\),我们想逆变换回去,只需要乘上正变换的逆矩阵。

\(a_x,a_{x-3^i},a_{x-2\times3^i}\) 分别为 \(a_2,a_1,a_0\),根据题意有 \(a'_0=a_0+a_1,a'_1=a_0+a_1+a_2,a'_2=a_1+a_2\)。写出转移矩阵。

\[\begin{bmatrix}1 & 1 & 0\\1 & 1 & 1\\0 & 1 & 1 \end{bmatrix} \]

矩阵求逆,然后展开回去即可得到逆变换的式子。

\[\begin{bmatrix}0 & 1 & -1\\1 & -1 & 1\\-1 & 1 & 0 \end{bmatrix} \]

#include <bits/stdc++.h>
using namespace std;
int n,k,f[600000],pw[20];
int getpos(int x,int p)
{while(p--)x/=3;return x%3;
}void fmt_swp(int f[])
{for(int i=0;i<k;i++)for(int j=0;j<n;j++)if(getpos(j,i)==2){int x=f[j-pw[i]*2],y=f[j-pw[i]],z=f[j];f[j-pw[i]*2]=y-z;f[j-pw[i]]=x-y+z;f[j]=y-x;}
}int main()
{pw[0]=1;for(int i=1;i<=12;i++)pw[i]=pw[i-1]*3;scanf("%d",&k);n=pw[k];for(int i=0;i<n;i++)scanf("%d",&f[i]);fmt_swp(f);for(int i=0;i<n;i++)printf("%d ",f[i]);return 0;
}

例题 \(9\)

P9131 [USACO23FEB] Problem Setting P

H 视为 \(1\)E 视为 \(0\),预处理出所有问题的状态 \(S\),题目限制转化为排在前面的问题的状态是排在后面的问题的状态的子集。

考虑 DP,但相同状态之间可能会互相转移,因此我们把状态相同的问题放在一起转移。具体的,设状态为 \(S\) 的问题有 \(c_S\) 个,若选择了至少一个,由于顺序有关,贡献为如下式子,记作 \(v_S\),可以预处理的时候暴力算出来。

\[v_S=\sum_{i=1}^{c_S}\binom{c_S}{i}i! \]

\(f_S\) 表示现在的状态为 \(S\) 的方案数,暂且忽略转移顺序,只考虑加入所有状态为 \(T\) 的问题的转移,有如下式子。

\[f'_T=f_T+\sum_{S\subsetneq T}f_Sv_T \]

可以变形为 \(f'_T=v_T\sum_{S\subseteq T}f_S-(v_T-1)f_T\)。发现 \(\sum_{S\subseteq T}f_S\) 就是 \(\text{FMT}\),于是对 \(f\) 做一遍 \(\text{FMT}\) 直接更新 \(f'_T\),其余继承,就完成了一个 \(T\) 的转移。

考虑应该按照什么顺序转移。显然,\(|S|\) 较大的 \(S\) 会从 \(|S|\) 较小的 \(S\) 转移过来,因此,我们按照 \(|S|\) 转移,即可保证转移任何一个状态时需要用到的状态均被计算过。初始时对所有 \(S\)\(f_S=v_S\),因为可以从任何一个状态开始。

需要注意如果要用到 \(\text{FMT}\),必须做完整的 \(\text{FMT}\),不能只在 \(\text{FMT}\) 中转移 \(|S|\) 等于当前枚举的 \(|S|\)\(S\)。因此,我们从小到大考虑 \(|S|\),每次整体做一遍 \(\text{FMT}\),将所有 \(|S|\) 等于当前枚举的 \(|S|\)\(S\) 转移出来。时间复杂度 \(O(m^22^m)\),可以通过。

#include <bits/stdc++.h>
using namespace std;
int n,m,k,f[2000000],g[2000000],cnt[2000000],v[2000000],jc[200000],inv[200000],ans=0;
char s[30][200000];
const int mod=1e9+7;
int power(int a,int p)
{int x=a,ans=1;while(p){if(p&1)ans=1ll*ans*x%mod;p>>=1;x=1ll*x*x%mod;}return ans;
}int get_a(int n,int k)
{return 1ll*jc[n]*inv[n-k]%mod;
}void fmt_or(int f[])
{for(int i=0;i<k;i++)for(int j=0;j<n;j++)if(j&(1<<i))f[j]=(f[j]+f[j^(1<<i)])%mod;
}int main()
{scanf("%d%d",&n,&m);jc[0]=1;for(int i=1;i<=n;i++)jc[i]=1ll*jc[i-1]*i%mod;inv[n]=power(jc[n],mod-2);for(int i=n-1;i>=0;i--)inv[i]=1ll*inv[i+1]*(i+1)%mod;for(int i=1;i<=m;i++)scanf("%s",s[i]+1);for(int i=1;i<=n;i++){int id=0;for(int j=1;j<=m;j++)id=(id<<1)+(s[j][i]=='H');cnt[id]++;}k=m,n=(1<<k);for(int i=0;i<n;i++)for(int j=1;j<=cnt[i];j++)v[i]=(v[i]+get_a(cnt[i],j))%mod;for(int i=0;i<n;i++)f[i]=v[i];for(int i=1;i<=k;i++){for(int j=0;j<n;j++)g[j]=f[j];fmt_or(g);for(int j=0;j<n;j++)if(__builtin_popcount(j)==i)f[j]=(1ll*g[j]*v[j]%mod-1ll*f[j]*(v[j]-1)%mod+mod)%mod;}for(int i=0;i<n;i++)ans=(ans+f[i])%mod;printf("%d\n",ans);return 0;
}

值得一提的是,使用 FMT 优化 DP 时,大部分情况下转移之间是有依赖性的,即大集合需要先求出小集合的答案,这种问题被称作半在线卷积。半在线卷积的一般处理方式是按照集合大小转移,和这道例题一样。

例题 \(10\)

P6570 [NOI Online #3 提高组] 优秀子序列

注意到,当所有集合不交时,\(a_i+a_j\) 等价于 \(a_i\cup a_j\),因此价值中的和式可以转化为交集,转化为了子集卷积。

考虑子序列中所有 \(a_i\) 的或和为 \(S\) 的子序列的数量,写出它的集合幂级数 \(f\)。最后的答案即为 \(\sum_{S\in U}f_S\varphi(1+S)\)

在原序列中选出一个子序列和选出一个子集没有本质区别,问题转化为选出若干个不交的数组成或和为 \(S\) 的方案数。由于选择的顺序无关,所以相当于把 \(S\) 分成若干个无标号的部分,这恰好是 \(\exp\) 的组合意义,直接对初值的集合幂级数 \(g\)\(\exp\) 即可求出 \(f\)

考虑初值,每种数 \(S\) 的选法为 \(S\) 在原序列中的出现次数 \(c_S\),根据 \(\exp\) 的组合意义有 \(g_S=c_S\)。因此,我们用桶记录每一种 \(S\) 的出现次数,直接做子集卷积意义下的 \(\exp\),再按照上面的答案计算式即可。

\(\exp\) 要求常数项为 \(0\),因此我们需要特判原序列中的空集:不记录进桶,但空集可以任意选,最后给答案乘上 \(2\) 的空集数量次方。

#include <bits/stdc++.h>
using namespace std;
int n,k,a[2000000],t[600000],f[20][600000],c[600000][20],g[20],inv[20],pr[600000],phi[600000],cnt=0,ans=0,c0=0;
bool vis[600000];
const int mod=1e9+7;
int power(int a,int p)
{int x=a,ans=1;while(p){if(p&1)ans=1ll*ans*x%mod;p>>=1;x=1ll*x*x%mod;}return ans;
}void init(int mx)
{vis[1]=1,phi[1]=1;for(int i=2;i<=mx;i++){if(!vis[i])pr[++cnt]=i,phi[i]=i-1;for(int j=1;j<=cnt&&i*pr[j]<=mx;j++){vis[i*pr[j]]=1,phi[i*pr[j]]=phi[i]*pr[j];if(i%pr[j]==0)break;else phi[i*pr[j]]=phi[i]*(pr[j]-1);}}
}void fmt_or(int f[],int fl)
{for(int i=0;i<k;i++)for(int j=0;j<n;j++)if(j&(1<<i))f[j]=(1ll*f[j]+f[j^(1<<i)]*fl+mod)%mod;
}void exp(int f[])
{for(int i=0;i<=k;i++)g[i]=0;g[0]=1;for(int i=1;i<=k;i++){for(int j=1;j<=i;j++)g[i]=(g[i]+1ll*f[j]*j%mod*g[i-j]%mod)%mod;g[i]=1ll*g[i]*inv[i]%mod;}for(int i=0;i<=k;i++)f[i]=g[i];
}int main()
{init(500000);scanf("%d",&n);for(int i=1;i<=n;i++){scanf("%d",&a[i]);if(a[i])t[a[i]]++;else c0++;}k=18,n=(1<<k);for(int i=0;i<n;i++)f[__builtin_popcount(i)][i]=t[i];for(int i=1;i<=k;i++)inv[i]=power(i,mod-2);for(int i=0;i<=k;i++)fmt_or(f[i],1);for(int i=0;i<n;i++){for(int j=0;j<=k;j++)c[i][j]=f[j][i];exp(c[i]);}for(int i=0;i<=k;i++)for(int j=0;j<n;j++)f[i][j]=c[j][i];for(int i=0;i<=k;i++)fmt_or(f[i],-1);for(int i=0;i<n;i++)ans=(ans+1ll*f[__builtin_popcount(i)][i]*phi[i+1]%mod)%mod;ans=1ll*ans*power(2,c0)%mod;printf("%d\n",ans);return 0;
}

例题 \(11\)

LOJ #154. 集合划分计数

将一个集合划分成 \(k\) 个不交的无标号集合,这就是集合幂级数 k-exp 的组合意义,直接对初值的集合幂级数 \(g\) 做 k-exp 即为答案。

考虑初值,每种数 \(S\) 的选法为 \(S\) 在原序列中的出现次数 \(c_S\),根据 \(\exp\) 的组合意义有 \(g_S=c_S\)。略微卡常。

#include <bits/stdc++.h>
using namespace std;
int n,m,k,p,v[300000],t[4000000],f[22][4000000],g[4000000],h[4000000],inv[22],jnv[22],a[4000000][22];
const int mod=998244353;
inline int read()
{int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}while(ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}return x*f;
}inline int power(int a,int p)
{int x=a,ans=1;while(p){if(p&1)ans=1ll*ans*x%mod;p>>=1;x=1ll*x*x%mod;}return ans;
}void fmt_or(int f[],int fl)
{for(int i=0;i<k;i++)for(int j=0;j<n;j+=(1<<(i+1)))for(int l=0;l<(1<<i);l++)f[(1<<i)+j+l]=(f[(1<<i)+j+l]+f[j+l]*fl)%mod;
}inline void exp(int f[])
{for(int i=1;i<=k;i++)g[i]=0;g[0]=1;for(int i=0;i<=k;i++)f[i]=1ll*f[i]*i%mod;for(int i=1;i<=k;i++){for(int j=0;j<i;j++)g[i]=(g[i]+1ll*g[j]*f[i-j])%mod;g[i]=1ll*g[i]*inv[i]%mod;}for(int i=0;i<=k;i++)f[i]=g[i];
}inline void ln(int f[])
{for(int i=0;i<=k;i++)g[i]=0;for(int i=1;i<=k;i++){for(int j=1;j<i;j++)g[i]=(g[i]+1ll*g[j]*f[i-j])%mod;g[i]=(1ll*i*f[i]-g[i])%mod;}for(int i=0;i<=k;i++)f[i]=1ll*g[i]*inv[i]%mod;
}inline void power(int f[],int p)
{int t=0,c=0,inv=0,pw=0;while(f[t]==0&&t<=k)t++;if(t==k+1)return;c=f[t],inv=power(c,mod-2),pw=power(c,p);for(int i=0;i<=k;i++)f[i]=1ll*f[i]*inv%mod;if(t)for(int i=0;i+t<=k;i++)f[i]=f[i+t],f[i+t]=0;ln(f);for(int i=0;i<=k;i++)f[i]=1ll*f[i]*p%mod;exp(f);if(1ll*p*t>=k+1)for(int i=0;i<=k;i++)f[i]=0;else {for(int i=k;i>=p*t;i--)f[i]=f[i-p*t],f[i-p*t]=0;for(int i=0;i<p*t;i++)f[i]=0;for(int i=p*t;i<=k;i++)f[i]=1ll*f[i]*pw%mod;}
}inline void kexp(int f[],int p)
{for(int i=0;i<=k;i++)h[i]=f[i];power(h,p);for(int i=0;i<=k;i++)h[i]=1ll*h[i]*jnv[p]%mod,g[i]=0;int pw=1;for(int i=0;i<=p;i++)g[0]=(g[0]+1ll*pw*jnv[i])%mod,pw=1ll*pw*f[0]%mod;for(int i=0;i<=k;i++)f[i]=1ll*f[i]*i%mod;for(int i=1;i<=k;i++){for(int j=0;j<i;j++)g[i]=(g[i]+1ll*(g[j]-h[j])*f[i-j])%mod;g[i]=1ll*g[i]*inv[i]%mod;}for(int i=0;i<=k;i++)f[i]=g[i];
}int main()
{n=read(),m=read(),p=read();for(int i=1;i<=m;i++)v[i]=read(),t[v[i]]++;k=n,n=(1<<k);for(int i=0;i<n;i++)f[__builtin_popcount(i)][i]=t[i];jnv[0]=1;for(int i=1;i<=k;i++)inv[i]=power(i,mod-2),jnv[i]=1ll*jnv[i-1]*inv[i]%mod;for(int i=0;i<=k;i++)fmt_or(f[i],1);for(int i=0;i<n;i++){for(int j=0;j<=k;j++)a[i][j]=f[j][i];kexp(a[i],p);}for(int i=0;i<=k;i++)for(int j=0;j<n;j++)f[i][j]=a[j][i];for(int i=0;i<=k;i++)fmt_or(f[i],-1);printf("%d\n",(f[__builtin_popcount(n-1)][n-1]+mod)%mod);return 0;
}

例题 \(12\)

P11734 [集训队互测 2015] 胡策的统计

考虑套用连通性限制中问题的结论,通过多项式 \(\ln\) 求出每个子图的连通生成子图数量。

考虑钦定只有若干个子集之间有连边,每个子集间形成连通块。考虑每个点能走到的节点,发现并不会算重。因此,一个子图是由若干个无标号的连通块组合成的。考虑集合幂级数的幂的组合意义有由 \(n\) 个连通块组成的子图数量为 \(\frac{f^n}{n!}\),除以 \(n!\) 是因为连通块无标号。再乘上 \(n!\) 的组合系数,由 \(n\) 个连通块组成的子图的贡献为 \(f^n\)。最终的答案为如下式子。

\[\sum_{n\ge 0}f^n \]

等比数列求和有 \(\frac{1}{1-f}\),是多项式复合的形式。先对每一个点值处的多项式变成 \(1-F(x)\),再多项式求逆。最后 \(\text{FMT}\) 回去。

本题卡常,FMT 是卡常版本,可以借鉴。

#include <bits/stdc++.h>
using namespace std;
int n,m,u[500],v[500],pw[500],iv[21],e[2000000],f[21][2000000],a[2000000][21],g[22];
const int mod=998244353;
int power(int a,int p)
{int x=a,ans=1;while(p){if(p&1)ans=1ll*ans*x%mod;p>>=1;x=1ll*x*x%mod;}return ans;
}void fmt_or(int f[],int fl)
{for(int i=0;i<n;i++)for(int j=0;j<(1<<n);j+=(1<<(i+1)))for(int k=0;k<(1<<i);k++)f[(1<<i)+j+k]=(f[(1<<i)+j+k]+f[j+k]*fl)%mod;
}void ln(int f[])
{for(int i=0;i<=n;i++)g[i]=0;for(int i=1;i<=n;i++){for(int j=1;j<=i-1;j++)g[i]=(g[i]+1ll*g[j]*j%mod*f[i-j])%mod;g[i]=(f[i]-1ll*g[i]*iv[i]+mod)%mod;}for(int i=0;i<=n;i++)f[i]=g[i];
}void inv(int f[])
{for(int i=0;i<=n;i++)g[i]=0;g[0]=power(f[0],mod-2);for(int i=1;i<=n;i++){for(int j=1;j<=i;j++)g[i]=(g[i]+1ll*f[j]*g[i-j])%mod;g[i]=(mod-1ll*g[i]*g[0]+mod)%mod;}for(int i=0;i<=n;i++)f[i]=g[i];
}int main()
{scanf("%d%d",&n,&m);for(int i=1;i<=m;i++)scanf("%d%d",&u[i],&v[i]),u[i]--,v[i]--;pw[0]=1;for(int i=1;i<=m;i++)pw[i]=pw[i-1]*2%mod;for(int i=0;i<(1<<n);i++)for(int j=1;j<=m;j++)if((i&(1<<u[j]))&&(i&(1<<v[j])))e[i]++;for(int i=0;i<(1<<n);i++)f[__builtin_popcount(i)][i]=pw[e[i]];for(int i=0;i<=n;i++)fmt_or(f[i],1),iv[i]=power(i,mod-2);for(int i=0;i<(1<<n);i++){for(int j=0;j<=n;j++)a[i][j]=f[j][i];ln(a[i]);for(int j=0;j<=n;j++)a[i][j]=(1-a[i][j]+mod)%mod;inv(a[i]);}for(int i=0;i<=n;i++)for(int j=0;j<(1<<n);j++)f[i][j]=a[j][i];for(int i=0;i<=n;i++)fmt_or(f[i],-1);printf("%d\n",(f[n][(1<<n)-1]+mod)%mod);return 0;
}

例题 \(13\)

AT_arc105_f [ARC105F] Lights Out on Connected Graph

题目等价于数连通二分生成子图。由于我们可以通过集合幂级数 ln 去掉连通性的限制,于是我们先考虑数二分生成子图,设出这个问题的集合幂级数 \(g\)

二分图有两个部分,每部分内部没有连边,部分之间的边任意连。记 \(e(S)\)\(S\) 集合内部的边的数量,则\(S,T(T\subseteq S)\) 之间的边的数量为 \(2^{e(S)-e(T)-e(S\backslash T)}\)。对于集合 \(S\),我们枚举子集 \(T\),有如下表达式。

\[g_S=\sum_{T\subseteq S}2^{e(S)-e(T)-e(S\backslash T)}=2^{e(S)}\sum_{T\subseteq S}2^{-e(T)}\times 2^{-e(S\backslash T)} \]

这是一个子集卷积的形式,令 \(g_S=2^{-e(S)}\),则 \(g\) 和自己子集卷积就可以得到 \(f\)。然后做 \(\ln\),发现过不去。

注意到,如果二分图不连通,对于每个连通块,我们的 \(T\) 都可以选择左部点或右部点,而连边不变。这样就意味着,如果一个方案有 \(n\) 个连通块,那么这个方案会被计算 \(2^n\) 次。如果系数与连通块有关,有一个经典的技巧是展开 \(\exp\) 的式子,\(f^n\) 表示的便是有 \(n\) 个连通块的情况,乘上对应系数就能得到想要的式子。

\[g=\sum_{n\ge 0}\frac{f^n2^n}{n!}=\sum_{n\ge 0}\frac{(2f)^n}{n!} \]

因此 \(g=\exp (2f)\),所以有 \(f=\frac{1}{2}\ln g\)。对 \(g\) 做完 \(\ln\) 之后再除以 \(2\) 即可通过。除以 \(2\) 也是复合的形式,转化为对每个点值处的多项式除以 \(2\)

#include <bits/stdc++.h>
using namespace std;
int n,m,u[400],v[400],e[200000],pw[400],pi[400],inv[20],f[20][200000],g[20][200000],a[200000][20];
const int mod=998244353;
int power(int a,int p)
{int x=a,ans=1;while(p){if(p&1)ans=1ll*ans*x%mod;p>>=1;x=1ll*x*x%mod;}return ans;
}void fmt_or(int f[],int fl)
{for(int i=0;i<n;i++)for(int j=0;j<(1<<n);j++)if(j&(1<<i))f[j]=(1ll*f[j]+f[j^(1<<i)]*fl+mod)%mod;
}void ln(int f[])
{int g[20];for(int i=0;i<=n;i++)g[i]=0;for(int i=1;i<=n;i++){for(int j=1;j<=i-1;j++)g[i]=(g[i]+1ll*g[j]*j%mod*f[i-j]%mod)%mod;g[i]=(f[i]-1ll*g[i]*inv[i]%mod+mod)%mod;}for(int i=0;i<=n;i++)f[i]=g[i];
}int main()
{scanf("%d%d",&n,&m);for(int i=1;i<=m;i++)scanf("%d%d",&u[i],&v[i]),u[i]--,v[i]--;pw[0]=pi[0]=1;for(int i=1;i<=m;i++)pw[i]=pw[i-1]*2%mod,pi[i]=499122177ll*pi[i-1]%mod;for(int i=0;i<(1<<n);i++)for(int j=1;j<=m;j++)if((i&(1<<u[j]))&&(i&(1<<v[j])))e[i]++;for(int i=0;i<(1<<n);i++)f[__builtin_popcount(i)][i]=pi[e[i]];for(int i=0;i<=n;i++)fmt_or(f[i],1);for(int i=0;i<=n;i++)for(int j=0;j<=i;j++)for(int k=0;k<(1<<n);k++)g[i][k]=(g[i][k]+1ll*f[j][k]*f[i-j][k]%mod)%mod;for(int i=0;i<=n;i++)fmt_or(g[i],-1);for(int i=0;i<(1<<n);i++)g[__builtin_popcount(i)][i]=1ll*g[__builtin_popcount(i)][i]*pw[e[i]]%mod;for(int i=0;i<=n;i++)fmt_or(g[i],1),inv[i]=power(i,mod-2);for(int i=0;i<(1<<n);i++){for(int j=0;j<=n;j++)a[i][j]=g[j][i];ln(a[i]);}for(int i=0;i<=n;i++)for(int j=0;j<(1<<n);j++)g[i][j]=a[j][i];for(int i=0;i<=n;i++)fmt_or(g[i],-1);printf("%lld\n",499122177ll*g[n][(1<<n)-1]%mod);return 0;
}

后记

学完集合幂级数,猛然一想:最近几年省选考的不是有向图计数吗?和集合幂级数有啥关系?不过也没事,这东西感觉挺有意思的,不亏qwq。

风骤紧 缥缈峰头云乱

红颜弹指老 刹那芳华

梦里真真语真幻

同一笑 到头万事俱空

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1172922.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

IoU损失函数新突破!Inner-IoU 让 YOLOv11 检测精度显著提升 | 完整实现教程

文章目录 目标检测损失函数革新:Inner-IoU 助力 YOLOv11 精度飙升实战教程 一、Inner-IoU 核心原理:辅助边框如何打破 IoU 瓶颈? 1. 传统 IoU 损失的局限性 2. Inner-IoU 的创新设计:辅助边框的魔力 3. 实验效果:用数据说话 二、Inner-IoU 集成到 YOLOv11 全流程:从代码修…

.Net base software framework - pattern, configuration and tool

.Net base software framework - pattern, configuration and toolPage 21 Genric constraintC# generic constraint is a way to specify requirements on the type parameter of a generic class or method. It allo…

Cloud Connector and Plugin

Cloud Connector and PluginTelecom multi service operator business tool to organize job fullfillment of 10k field repair and install technician 3 self contain microservices - loosely coupling architectu…

YOLOv13 模块改造实战:从零集成 SFSConv 提升目标检测精度(保姆级教程)

零基础手把手教程:在 YOLOv13 中集成 SFSConv,让目标检测更上一层楼 文章目录 零基础手把手教程:在 YOLOv13 中集成 SFSConv,让目标检测更上一层楼 前言 目录 1. 为什么需要 SFSConv?传统 CNN 的局限与高级特征的需求 1.1 传统卷积的特点与不足 1.2 多尺度特征的重要性 1.…

UNet++MobileNetv2模型优化,RK3588部署效率飙升300%

文章目录【保姆级教程】基于UNet&MobileNetv2的语义分割模型从训练到RK3588部署&#xff1a;让边缘AI落地效率提升300%引读一、技术选型与场景价值二、环境搭建&#xff1a;从云端到边缘的工具链闭环1. 云端训练环境&#xff08;Python生态&#xff09;2. 模型转换工具链&a…

处理完ACPI!AcpiBuildRunMethodList链表后返回要检查acpi!AcpiBuildQueueList链表不空运行continue继续循环

处理完ACPI!AcpiBuildRunMethodList链表后返回要检查acpi!AcpiBuildQueueList链表不空运行continue继续循环0: kd> g Breakpoint 5 hit eax00000000 ebx00000000 ecx89906e40 edx00000001 esi89906e30 edi80b019f4 eipf73fb911 espf789ef68 ebpf789ef84 iopl0 nv up…

Angular Interview

Angular InterviewAngular.json just like csproj in c# to defined elements needed to build and run the angular app, it provides application entry point, first view landed, angular defined styles, ts->…

第二次运行ACPI!ACPIBuildProcessQueueList函数链表内的buildRequest->TargetListEntry都是ACPI!AcpiBuildRunMethodList

第二次运行ACPI!ACPIBuildProcessQueueList函数链表内的buildRequest->TargetListEntry都是ACPI!AcpiBuildRunMethodListVOID ACPIBuildDeviceDpc(IN PKDPC Dpc,IN PVOID DpcContext,IN PVOID SystemArgument1,IN PVOID SystemArgument2) {do {//// Assume that…

CSDN首页发布文章请输入文章标题(5~100个字) 还需输入5个字摘要:会在推荐、列表等场景外露,帮助读者快速了解内容,支持一键将正文前 256 字符键入摘要文本框0 / 256A

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

RDBMS interview questions

RDBMS interview questions

攻克低照度检测难题:YOLOv11主干网络增强新方案PE-YOLO详解

购买即可解锁300+YOLO优化文章,并且还有海量深度学习复现项目,价格仅需两杯奶茶的钱,别人有的本专栏也有! 文章目录 YOLOv11低照度增强主干网络PE-YOLO:原理与完整实现教程 算法核心原理 物理模型基础 网络架构设计 完整代码实现 环境配置与依赖 PE模块网络定义 集成PE模…

精度损失1.6%,速度提升10.3 FPS:YOLOv8稀疏训练+Slim剪枝高效压缩方案

剪枝对比 文章目录 slim论文解读:2017年 Slim剪枝(Network Slimming)简介 核心思想 1. **在BatchNorm层中的缩放因子(γ)上引入 L1 正则化** Slim剪枝的步骤 第一步:训练阶段加正则 第二步:通道剪枝 第三步:微调(Fine-tuning) 移植代码 下载yolov8代码 在工作根目录…

完整教程:【Linux】常用指令

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

Map提升4.89%!YOLOv13融合RepVGG-OREPA与SE注意力的多分支设计

绿色线条为添加RepVGGBlock后的效果,map提升4.89,召回率提升8.66 REPVGGOREPA模块代表了重参数化技术的最新发展,它通过巧妙的架构设计实现了训练时的丰富表达和推理时的高效计算。 文章目录 移植 创建ultralytics\cfg\models\v13\yolov13-REPVGGOREPA.yaml 修改ultralytics\…

Preliminary Design - EM driven VSP restart (VUEDPI-3838)

Preliminary Design - EM driven VSP restart (VUEDPI-3838)Story VUEDPI-3838: Ability to restart specific program container from a specific Splicer/RC Background: Frontend: Backend: Work Breakdown and Est…

YOLO-World:从入门到实战的多模态目标检测全指南

文章目录 从0到1掌握YOLO-World:多模态目标检测入门到实战超详细教程 一、先搞懂“多模态目标检测”和YOLO-World到底是什么 1. 什么是多模态目标检测? 2. YOLO-World:速度与精度的多模态标杆 二、YOLO-World的技术逻辑:从输入到输出的全流程 1. 核心架构:“图像-文本”双…

JavaAPI 工具类

工具类 Math public static int abs (int a) 获取参数绝对值 public static duble ceil (doouble a)向上取整 public static duble floor (doouble a)向下取整 public static int round(float a) 四舍五入 public static int max (int a,int b)获取两个int…

create_deep_agent vs create_agent 的区别

目录 1. create_agent - LangChain 标准函数 2. create_deep_agent - DeepAgents 高级函数 核心区别对比 实际应用对比 工作流程对比 何时使用哪个&#xff1f; 总结 1. create_agent - LangChain 标准函数 来源&#xff1a; langchain.agents 作用&#xff1a; 创建基…

不要让几十万血汗钱打水漂!河北农村自建房必须要了解的7个问题,不懂真的亏大了! - 苏木2025

在河北,从冀北张家口蔚县、承德围场的山地丘陵,到冀中保定清苑、石家庄藁城的平原沃野,再到冀南邯郸永年、邢台宁晋的农耕区,以及冀东唐山滦南、秦皇岛昌黎的沿海村镇,农村自建房始终是家家户户的头等大事。对于大…

基于VUE的高校毕业设计管理系统[VUE]-计算机毕业设计源码+LW文档

摘要&#xff1a;高校毕业设计管理是一项复杂且重要的工作&#xff0c;传统管理方式在效率、准确性等方面存在诸多不足。本文旨在设计并实现基于VUE的高校毕业设计管理系统&#xff0c;以提升管理效能。通过深入的需求分析&#xff0c;明确系统涵盖用户管理、选题管理、任务书管…