子集卷积求逆
公式:\((f*g)_I=\sum_{S\cap T=\varnothing,S\cup T=I}f_S\cdot g_T\)。
【单位元】
\(\varepsilon=x^0\) 是子集卷积的单位元。注意 \(\varepsilon\) 是一个集合幂级数。容易验证。
【求逆】
给定 \(f=(f_0,\dots,f_{2^n-1})\),求 \(g\) 使得 \(f*g=\varepsilon\)。
首先考虑 \(f\) 是否一定存在逆。
如果 \(f_0=0\),不可能有逆。因为无论 \(g\) 怎么选,\((f*g)_0=f_0g_0=0\neq \varepsilon_0\)。
否则我们可以证明有唯一的逆。
用一个例子表述。\(n=3\)。
\((f*g)_{000}=\varepsilon_0=1=f_{000}\times g_{000}\),可以解出 \(g_{000}\)。
\((f*g)_{001}=0\),则有 \(f_{000}g_{001}+f_{001}g_{000}\),根据上面的结果可以解出 \(g_{001}\)。
以此类推,当我们要解 \(g_S\) 的时候,\(g_T|T\subseteq S\) 都已经解出来了。
但是如果用证明的方法来解,是 \(O(3^n)\) 的:因为对每一个 \(g_S\) 都要枚举它的子集计算。
下面同样介绍两种方法优化到 \(O(n^22^n)\)。也是一种用到占位幂级数一种不要。
【法一】
不需要用到占位集合幂级数。
首先 \(g_{0}=f_0^{-1}\) 是好求的。
然后 \(\forall S\neq \varnothing,\sum_{T\subseteq S}f_T\cdot g_{S-T}=0\)。把其中 \(f_0\) 项挪出来,得到 \(f_0g_S=-\sum_{T\subseteq S,T\neq \varnothing}f_T\cdot g_{S-T}\)。移项有
\(-f_0^{-1}\) 是好处理的,重点在于 \(\sum_{T\subsetneq S}f_{S-T}\cdot g_T\) 怎么做。
这个长得很类似分治 FFT 里的半在线(自己卷另一个),不过换成了子集卷积。
类似考虑按 \(S\) 的大小把 \(f,g\) 分类,\(g^{(0)}\) 是好求的(只有空集一处有系数)。然后用 \(f^{(1)}*g^{(0)}=g^{(1)}\)。以此类推。
-
\(f^{(1)}*g^{(0)}\),取其中 \(|S|=1\) 的项得到 \(g^{(1)}\)。
-
\(f^{(2)}*g^{(0)}+f^{(1)}*g^{(1)}\),取其中 \(|S|=2\) 的项得到 \(g^{(2)}\)。
-
以此类推。
这里不用每次求新 \(g^{(i)}\) 都重新做子集卷积。可以提前把 \(\mathrm{DMT}(f^{(0\sim n)})\) 算出来。每算出一个 \(g^{(i)}\) 就立刻算其 DMT。一共 \(2n\) 次 DMT,复杂度和普通子集卷积是一样的 \(O(n^22^n)\)。
【法二】
需要占位集合幂级数。带 \(z\)。不记得了就回普通子集卷积看。
为了区分,把普通集合幂级数用小写 \(f\),占位集合幂级数用大写 \(F\)。
大体思路是 \(f\) 变成 \(F\),求出 \(F\) 或卷积的逆 \(G\) 后,再消除占位得到 \(g\)。这个思路的正确性证明放在后面,先说实现。
注:占位幂级数就是 \(g_I=[x^Iz^{|I|}]G\),"合法项"。
先说明存在 \(F\) 的逆 \(G\) 使得 \(F*G=\varepsilon\)。等价于 \(\overline{F}\cdot \overline{G}=\overline{\varepsilon}\)。而 \(\varepsilon\) 或卷积后每一项都是 \(1\)。所以 \(\overline{F}_I\cdot \overline{G}_I=1\)。
因为是占位幂级数,所以 \(F\) 的每一项系数都是一个关于 \(z\) 的多项式。进一步观察,其常数项为 \(f_0\)。为什么呢?因为常数意味着 \(z\) 的指数是 \(0\)。而 \(F=\sum f_Ix^Iz^{|I|}\),只有 \(I=\varnothing\) 时 \(z\) 的指数才是 \(0\),所以常数项就是 \(f_0\)。而常数项非 \(0\) 表明多项式可逆,所以 \(G\) 存在。
先考虑 \(F\rightarrow G\),分三步走。
-
DMT 求 \(\overline{F}\)。
-
为每个 \(I\) 求出 \(\overline{F}_I\) 的逆,得到 \(\overline{G}\)。
-
iDMT 求 \(G\)。
第一三步因为是多项式的 DMT,所以是 \(O(n^22^n)\) 的。
第二步对每个位置求逆可以直接暴力 \(O(n^2)\) 求逆(常数比 \(O(n\log n)\) 小,又快又不容易错),所以是 \(O(n^22^n)\) 的。
因此是 \(O(n^22^n)\)。
而从 \(f\rightarrow F,G\rightarrow g\) 显然是可以 \(O(2^n)\) 完成的。因此总复杂度 \(O(n^22^n)\)。
接下来说明这么做确实能得到 \(g\) 使得 \(f*g=\varepsilon\)。注意 \(f\times g=\varepsilon\)(子集卷积) 和 \(F*G=\varepsilon\) (或卷积)并不显然!
\(F=\sum f_Ix^Iz^{|I|}\),设 \(F*G=\varepsilon\),我们这么证:
-
先证明一个引理:\(G_I\) 中 \(z^j(j<|I|)\) 的系数为 \(0\)。
把 \(z\) 的指数大于 \(|I|\) 的称作大项,相应有小项。(指数 \(=|I|\) 是合法项)
先证 \(G_{01}\) 里无常数项。
因为 \(F*G=\varepsilon\),所以 \((F*G)_{01}=0\),而 \((F*G)_{01}=F_{00}\cdot G_{01}+F_{01}\cdot G_{00}+F_{01}\cdot G_{01}\)。(注意是或卷积不是子集卷积)
因为 \(F\) 是 \(f\) 的占位,里面只有合法项,所以 \(F_{01}\) 一定是 \(z\) 的倍数,所以 \(F_{01}G_{00}+F_{01}G_{01}\) 也是 \(z\) 的倍数,那么常数项之和 \(F_{00}G_{01}\) 有关。
而 \(F_{00}\) 里 \(z\) 的指数是 \(0\),是个常数项。要使得为 \(0\),必须 \(G_{01}\) 的常数项为 \(0\)。
同理可以证明 \(G_{10}\) 无常数项。
而 \(G_{11}\) 里无常数项和一次项。常数项是好证明的,类似可以知道要使得或卷积后常数项为 \(0\),\(F_{00}G_{11}\) 的常数项是 \(0\),而 \(F_{00}\) 是不为 \(0\) 常数,所以 \(G_{11}\) 常数项为 \(0\)。
而进一步观察式子:
后面的东西也都是 \(z^2\) 倍数。(\(G_{10},G_{01},G_{11}\) 无常数项,所以都是 \(z\) 的倍数)
那么 \(z\) 的系数又只和 \(F_{00}G_{11}\) 有关。所以 \(G_{11}\) 不能有一次项,否则和 \(F_{00}\) 一乘非 \(0\)。
-
\(f^{-1}\) 是 \(G\) 的占位还原。这一步就是原命题。
设 \(G\) 的占位还原为 \(g\)。有 \(F*G=\varepsilon\)(或卷积),求证 \(f\times g=\varepsilon\)(子集卷积)。
先证明 \((f\times g)_0=f_0\cdot g_0\)。
\((F*G)_0=1\Rightarrow F_0\cdot G_0=1\Rightarrow f_0\cdot G_0=1\Rightarrow f_0\cdot G_0\text{常数项}=1\)。
同时因为 \(G\) 占位还原后 \(g\),\(g\) 就是取 \(G_0\) 的常数项,所以 \(f_0\cdot g_0=1\)。
然后再证明 \(I\neq 0\) 时 \((f\times g)_I=0\)。
\(0=(F*G)_I=\sum_{J\cup K=I}F_JG_K=\sum_{J\cup K=I}(\sum_{t\ge |K|}f_Jz^{|J|}\cdot ([z^t]G_K)z^t)\)。
最后一步的转化用到了上面的引理。
令 \(i=|I|\),考虑这里面 \(z^i\) 的系数(必须有 \(t=i-|J|\))\(\sum_{J\cup K=I}f_J\cdot [z^t]G_K\).
因为 \(t=i-|J|\),且 \(t\ge |K|\),得到 \(|I|-|J|\ge |K|\)。由 \(J\cup K=I\) 知 \(|J|+|K|\ge |I|\),所以必然有 \(|J|+|K|=|I|\)。
可以化简式子为 \(\sum_{J\cup K=I,|J|+|K|=i}f_J[z^{|K|}]G_K=\sum_{J\cup K=I,|J|+|K|=|I|}f_Jg_{K}=0\)。
这就是 \(f,g\) 的子集卷积为 \(0\)。得证。
【例题】
下面三个改进的例子没有复杂度上的优化,但是能加深理解。
除法改进
输入 \(d,f\) 两个数组,求 \(q\) 使得 \(d\times q=f\)(子集卷积)。保证 \(d^{-1}\) 存在。
\(d\times q=f\iff d^{-1}\times d\times q=f\times d^{-1}\iff \varepsilon\times q=f\times d^{-1}\iff q=f/d\)
实现只需要先求 \(d\) 的逆,再和 \(f\) 子集卷积即可。复杂度 \(O(n^22^n)\)。
但是这里我们可以对法二再做点改进。
法二我们的流程是(记 \(g=d^{-1}\)):
-
\(d\) 占位得到 \(D\),\(D\) 求或卷积的逆得到 \(G\),\(G\) 占位还原得到 \(g\);
-
\(f\) 占位得到 \(F\),\(g\) 占位得到 \(G_0\);
-
\(F,G\) 做或卷积得到 \(Q\),再占位还原得到 \(q\)。
我们发现占位还原得到 \(g\) 后再占位这一步是没必要做的。
我们考虑 \(G\rightarrow g\rightarrow G_0\) 的过程,实际上是消去了所有不合法项。根据上面的结论,就是消除了所有大项。但我们没有必要把大项都消除,因为 \(F,G\) 或卷积之后大项只会更大,所以最终 \(Q\) 做占位还原的时候还是会把它们消除。
连乘改进
给定若干个数组,求它们的子集卷积。
类比上面,我们不需要每次乘法都求占位版本,乘完再占位还原。我们只需一开始就把所有集合幂级数的占位求出来,全部相乘,最后再进行一次占位还原即可。
先乘后逆改进
给定 \(g,h\) 两个数组,求 \((g\times h)^{-1}\)。(子集卷积)
算法流程:
-
\(g\) 占位成 \(G\),\(h\) 占位成 \(H\)。
-
\(G*H\)(或卷积)得到 \(F\)。
-
\(F\) 占位还原得到 \(f=g\times h\)。
-
\(f\) 占位得到 \(F_0\)。
-
\(F_0\) 求或卷积意义下的逆得到 \(Q\)。
-
\(Q\) 占位还原得到 \(q=(g\times h)^{-1}\)。
但 34 两步是没必要的,我们可以直接拿 \(F\) 求或逆得到 \(Q\) 再占位还原,得到的也是 \(q\)。
这个并不那么显然。证明一下。
设 \(f\) 占位加上若干大项后得到 \(F+B\)。\(F+B\) 求或卷积逆 \(G=(F+B)^{-1}\) 再占位还原得到 \(g\),还是 \(g=f^{-1}\)。
分两步证明。
-
\((F+B)^{-1}\) 不含小项。
简写 \(F+B=S\)。\(S*G=\varepsilon\)(或卷积)。我们要证明 \(G\) 不含小项。
-
\(G_{01}\) 不含小项(常数项)。
\((S*G)_{01}=S_{00}G_{01}+S_{01}(\cdots)=0\)。而 \(S_{01}\) 是 \(z\) 的倍数,所以要 \(=0\) 必须 \(G_{01}\) 不含常数项,否则和 \(S_{00}\) 配出一个非 \(0\) 常数。
-
类似 \(G_{10}\) 不含常数项。
-
\(G_{11}\) 不含常数项和一次项。
对常数项的证明类似,下证不含一次项。
\[(S*G)_{11}=S_{00}G_{11}+S_{01}(G_{10}+G_{11})+S_{10}(G_{01}+G_{11})+S_{11}(\cdots)=0 \]而 \(S_{01},S_{10},G_{01},G_{10},G_{11}\) 都是 \(z\) 的倍数,\(S_{11}\) 是 \(z^2\) 的倍数,所以一次项的系数之和 \(S_{00}G_{11}\) 有关。而 \(S_{00}\) 是个非 \(0\) 常数,所以 \(G_{11}\) 不含一次项。
-
后面证明类似。
-
-
\(g\times f=\varepsilon\)。
设 \(F^{-1}=l_1+B_1\)(合法项加一些大项),\((F+B)^{-1}=l_2+B_2\).
转证 \(l_1=l_2\) 即可。
\(F*(l_1+B_1)=(F+B)*(l_2+B_2)=\varepsilon\)。根据或卷积对加法的分配律,有:
\(F*l_1+\cdots =F*l_2+\cdots\),省略号里都是大项,记作 \(B\)。
所以 \(F*(l_1-l_2)=B\)。
两边同时乘以 \(F\) 或卷积的逆 \(F^{-1}\),得到 \(l_1-l_2=B*F^{-1}\)。
根据上面的结论,\(F^{-1}\) 只有合法项和大项(取 \(B=0\) 即可)。
所以 \(B*F^{-1}\) 里只有大项。
\(l_1-l_2\) 是若干合法项,右边是若干大项。两个相等,必须右边的系数都是 \(0\),也就是 \(l_1-l_2=0\),所以 \(l_1=l_2\)。
证毕。
有了上面三个例子,我们可以猜想:
\(f\) 的占位幂级数 \(F\) 加上任何一些随机的大项,都可以视作同一类,都可以叫做 \(f\) 的占位幂级数。(大项不影响实际运算,且中间结果不含小项)
在先乘后逆里,我们证明了子集卷积乘法和求逆都是可行的。
事实上这个猜想是对的。涉及其他运算的证明比较复杂,咕咕咕。
P6846 Amusement Park
有向图 \(G(n\le 18)\)。选若干条边翻转方向,使图变成 DAG。求出每种方案翻转边数的和。
首先我们发现这个翻转边数的和其实等价于求方案数。因为设 \(A\) 是一个合法方案,它的取反 \(\overline{A}\) 也合法,且两个方案翻转边数相加恰好等于 \(m\)。
考虑 DP 做。记一个顶点集合 \(X\) 的生成子图为 \(G_X\)。
对一个顶点集合 \(X\) 定义 \(dp(X)\):\(G_X\) 的方案数。
考虑 DAG 中入度为 \(0\) 的点,定义两个辅助数组。
-
\(f(S)\):入度为 \(0\) 的点恰好为 \(S\),是 DAG 的方案数。
-
\(g(S)\):入度为 \(0\) 的点至少为 \(S\),是 DAG 的方案数。
\(f,g\) 显然有:\(g(S)=\sum_{S\subseteq T}f(T)\Rightarrow f(S)=\sum_{S\subseteq T}(-1)^{|T|-|S|}g(T)\)。
\(dp(X)=\sum_{S\neq \varnothing}f(S)=\sum_{S\neq \varnothing}\sum_{S\subseteq T}(-1)^{|T|-|S|}g(T)=\sum_{T\neq \varnothing}g(T)(-1)^{|T|-1}\)。
而 \(g(S)=[{S 在}G_x{是独立集}]\times dp(X-S)\)。
所以 \(dp(X)=\sum_{T\neq\varnothing,T\subseteq X}[T在G_x是独立集](-1)^{|T|-1}dp(X-T)\)。
同时我们注意到,\(T\) 在 \(G_x\) 是独立集,等价于 \(T\) 在 \(G\) 中是独立集。(因为 \(T\subseteq X\))
我们把 \([T在G中是独立集](-1)^{|T|-1}\) 记作 \(C_T\)。
那么 \(dp(X)=\sum_{T\neq\varnothing}C_Tdp(X-T)\)。定义 \(C_{\varnothing}=0\),则 \(dp(X)=\sum_TC_Tdp(X-T)\)。
这是一个半在线子集卷积形式,可以 \(O(n^22^n)\) 完成。
但我们还有更好的理解。
把 \(dp\) 看作集合幂级数,则有 \(dp=C\times dp+1\)(子集卷积乘),则 \(dp=(1-C)^{-1}\)。
虽然复杂度还是 \(O(n^22^n)\)。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 18, MOD = 998244353;
int n, m;
int mtr[20][20] = {{}};
bool ind[(1 << N) + 5] = {}; //ind[i]=true表示i在原图是独立集
namespace Poly { //集合幂级数
ll fpow(ll a, ll b, ll p) {
ll mul = 1;
while (b) {
if (b & 1)
mul = mul * a % p;
a = a * a % p;
b >>= 1;
}
return mul;
}
ll mmi(ll a) {
return fpow(a, MOD - 2, MOD);
}
int lim;
struct poly {
ll a[(1 << N) + 5];
ll& operator[](int x) {
return a[x];
}
};
void neg(poly &a) { //每一项取反
for (int i = 0; i < (1 << lim); i++)
if (a[i])
a[i] = MOD - a[i];
}
poly operator+(poly a, int b) {
a[0] = ((a[0] + b) % MOD + MOD) % MOD;
return a;
}
poly operator+(poly &a, poly &b) {
poly c;
for (int i = 0; i < (1 << lim); i++)
c[i] = (a[i] + b[i]) % MOD;
return c;
}
void DMT(poly &f, int op = 1) {
for (int o = 2; o <= (1 << lim); o <<= 1)
for (int i = 0; i < (1 << lim); i += o)
for (int j = 0; j < (o >> 1); j++) {
(f[i + j + (o >> 1)] += f[i + j] * op % MOD) %= MOD;
(f[i + j + (o >> 1)] += MOD) %= MOD;
}
}
poly ta[N + 1], tb[N + 1], tc[N + 1];
inline int popcnt(int x) {
return __builtin_popcount(x);
}
//子集卷积
poly operator*(poly &a, poly &b) {
for (int i = 0; i <= lim; i++)
for (int j = 0; j < (1 << lim); j++)
ta[i][j] = tb[i][j] = 0;
for (int i = 0; i < (1 << lim); i++) {
ta[popcnt(i)][i] = a[i];
tb[popcnt(i)][i] = b[i];
}
for (int i = 0; i <= lim; i++) {
DMT(ta[i], 1);
DMT(tb[i], 1);
}
for (int i = 0; i <= lim; i++) {
for (int S = 0; S < (1 << lim); S++)
tc[i][S] = 0;
for (int j = 0; j <= i; j++)
for (int S = 0; S < (1 << lim); S++)
tc[i][S] = (tc[i][S] + ta[j][S] * tb[i - j][S] % MOD) % MOD;
DMT(tc[i], -1);
}
poly ret;
for (int S = 0; S < (1 << lim); S++)
ret[S] = tc[popcnt(S)][S];
return ret;
}
//子集卷积求逆
poly inv(poly &a) {
for (int i = 0; i <= lim; i++)
for (int j = 0; j < (1 << lim); j++)
ta[i][j] = tb[i][j] = 0;
for (int i = 0; i < (1 << lim); i++)
ta[popcnt(i)][i] = a[i];
for (int i = 0; i <= lim; i++)
DMT(ta[i], 1);
ll ans0 = mmi(a[0]);
tb[0][0] = ans0;
DMT(tb[0], 1);
for (int i = 1; i <= lim; i++) {
for (int j = 0; j < i; j++) {
for (int S = 0; S < (1 << lim); S++)
tb[i][S] = (tb[i][S] + tb[j][S] * ta[i - j][S] % MOD) % MOD;
}
DMT(tb[i], -1);
for (int S = 0; S < (1 << lim); S++) {
if (popcnt(S) != i)
tb[i][S] = 0;
else
tb[i][S] = (tb[i][S] * -ans0 % MOD + MOD) % MOD;
}
DMT(tb[i], 1);
}
//此时tb[0~lim]是DMT过后的
for (int i = 0; i <= lim; i++)
DMT(tb[i], -1);
poly ret;
for (int S = 0; S < (1 << lim); S++)
ret[S] = tb[popcnt(S)][S];
return ret;
}
}
using namespace Poly;
poly C;
poly dp;
void init() {
for (int i = 1; i <= n; i++)
ind[(1 << i - 1)] = true;
for (int S = 1; S < (1 << n); S++) {
if (ind[S])
continue;
for (int i = 0; i < n; i++)
if ((S >> i) & 1) {
if (ind[S - (1 << i)]) {
ind[S] = true;
for (int j = 0; j < n; j++)
if (((S >> j) & 1) && i != j)
ind[S] = ind[S] && !mtr[i + 1][j + 1] && !mtr[j + 1][i + 1];
}
break;
}
}
C[0] = 0;
for (int S = 1; S < (1 << n); S++)
C[S] = ind[S] * ((popcnt(S) % 2 == 1) ? 1 : -1);
}
void prework() {
init();
neg(C);
C = C + 1;
dp = inv(C);
}
int main() {
// freopen("1.in", "r", stdin);
// freopen("1.out", "w", stdout);
cin >> n >> m;
lim = n;
for (int i = 1, u, v; i <= m; i++) {
cin >> u >> v;
mtr[u][v] = 1;
}
prework();
cout << dp[(1 << n) - 1] * m % MOD * mmi(2) % MOD << endl;
return 0;
}

浙公网安备 33010602011771号