非常牛逼的东西
这东西理解成按位做 d p dp dp 就行,用于处理与位运算有关的贡献问题,使用条件是 贡献在每一位上独立,或者可以通过记录到状态里使得每一位独立
实际应用非常灵活,毕竟是 d p dp dp 嘛
对于下标为 [ 0 , 2 n − 1 ] [0,2^n-1] [0,2n−1] 的两个数组 A , B A,B A,B,定义它们的位运算卷积为 C k = ∑ i ⊗ j = k A i × B j C_k=\sum\limits_{i\otimes j=k} A_i\times B_j Ck=i⊗j=k∑Ai×Bj,其中 ⊗ \otimes ⊗ 是某种位运算,记作 C = A ⊗ B C=A\otimes B C=A⊗B
暴力做是 4 n 4^n 4n,想要加快这个过程,我们类比优化多项式卷积的思路,希望将 A , B A,B A,B 数组进行某种变换后得到 A ′ , B ′ A',B' A′,B′,满足 C k ′ = A k ′ ⋅ B k ′ C'_k=A'_k\cdot B'_k Ck′=Ak′⋅Bk′,然后再通过某种方式将 C ′ C' C′ 还原为 C C C
记这个变换为 F W T ( A ) FWT(A) FWT(A),定义 F W T ( A ) i = ∑ j = 0 2 n − 1 A j f ( i , j ) FWT(A)_i=\sum\limits_{j=0}^{2^n-1}A_jf(i,j) FWT(A)i=j=0∑2n−1Ajf(i,j),其中 f ( i , j ) f(i,j) f(i,j) 表示原数组 A A A 的第 j j j 项在自变量取 i i i 时的贡献方式 (特别的,我们会发现当 f ( i , j ) = i j f(i,j)=i^j f(i,j)=ij 时,恰好就是我们所熟悉的多项式点值)
我们希望这个变换能够用来加速卷积,符合上面的条件,即 F W T ( A ⊗ B ) = F W T ( A ) ⋅ F W T ( B ) FWT(A\otimes B)=FWT(A)\cdot FWT(B) FWT(A⊗B)=FWT(A)⋅FWT(B),同时我们希望它是一个线性变换,即 F W T ( A + B ) = F W T ( A ) + F W T ( B ) FWT(A+B)=FWT(A)+FWT(B) FWT(A+B)=FWT(A)+FWT(B) F W T ( k A ) = k F W T ( A ) FWT(kA)=kFWT(A) FWT(kA)=kFWT(A)
想要满足第一个式子, f ( i , j ) f(i,j) f(i,j) 需要满足 f ( i , j ) f ( i , k ) = f ( i , j ⊗ k ) f(i,j)f(i,k)=f(i,j\otimes k) f(i,j)f(i,k)=f(i,j⊗k),以下是推导:
∀ i , F W T ( A ⊗ B ) i = F W T ( A ) i ⋅ F W T ( B ) i \forall i,\ FWT(A\otimes B)_i=FWT(A)_i\cdot FWT(B)_i ∀i, FWT(A⊗B)i=FWT(A)i⋅FWT(B)i
∑ s = 0 2 n − 1 ( f ( i , s ) × ∑ p ⊗ q = s A p B q ) = ( ∑ j = 0 2 n − 1 A j f ( i , j ) ) ( ∑ k = 0 2 n − 1 B k f ( i , k ) ) \sum\limits_{s=0}^{2^n-1}( f(i,s)\times \sum_{p\otimes q=s}A_pB_q) = (\sum_{j=0}^{2^n-1}A_jf(i,j))(\sum_{k=0}^{2^n-1}B_kf(i,k)) s=0∑2n−1(f(i,s)×p⊗q=s∑ApBq)=(j=0∑2n−1Ajf(i,j))(k=0∑2n−1Bkf(i,k))
∑ s = 0 2 n − 1 ( f ( i , s ) × ∑ p ⊗ = s A p B q ) = ∑ j ∑ k A j B k f ( i , j ) f ( i , k ) \sum\limits_{s=0}^{2^n-1}( f(i,s)\times \sum_{p\otimes=s}A_pB_q) = \sum_j\sum_kA_jB_kf(i,j)f(i,k) s=0∑2n−1(f(i,s)×p⊗=s∑ApBq)=j∑k∑AjBkf(i,j)f(i,k)
与左边对照
∑ s = 0 2 n − 1 ( f ( i , s ) × ∑ p ⊗ = s A p B q ) = ∑ s = 0 2 n − 1 ∑ j ⊗ k = s A j B k f ( i , j ) f ( i , k ) \sum\limits_{s=0}^{2^n-1}( f(i,s)\times \sum_{p\otimes=s}A_pB_q) = \sum_{s=0}^{2^n-1}\sum_{j\otimes k=s}A_jB_kf(i,j)f(i,k) s=0∑2n−1(f(i,s)×p⊗=s∑ApBq)=s=0∑2n−1j⊗k=s∑AjBkf(i,j)f(i,k)
只要满足 f ( i , s ) = f ( i , j ) f ( i , k ) f(i,s)=f(i,j)f(i,k) f(i,s)=f(i,j)f(i,k) 即可,即 f ( i , j ⊕ k ) = f ( i , j ) f ( i , k ) f(i,j\oplus k)=f(i,j)f(i,k) f(i,j⊕k)=f(i,j)f(i,k)
注意以上推导没有用到任何位运算的性质,对所有卷积都是适用的
现在还是无法快速求出 F W T ( A ) FWT(A) FWT(A),我们考虑给 f ( i , j ) f(i,j) f(i,j) 加上一些位运算的性质:
要求 f ( i , j ) = ∏ k = 0 n − 1 f ( i k , j k ) f(i,j)=\prod_{k=0}^{n-1}f(i_k,j_k) f(i,j)=∏k=0n−1f(ik,jk),其中 i k i_k ik 表示二进制中 i i i 第 k k k 位上的值
接下来就可以分治计算 F W T ( A ) FWT(A) FWT(A) 了
F W T ( A ) i = ∑ j = 0 2 n − 1 A j f ( i , j ) FWT(A)_i=\sum\limits_{j=0}^{2^n-1}A_jf(i,j) FWT(A)i=j=0∑2n−1Ajf(i,j)
注意到去掉最高位之后两部分与原问题完全一致,设 c c c 为 i i i 最高为的值, i ′ , j ′ i',j' i′,j′ 为去掉最高为剩下的值
= f ( c , 0 ) ∑ j = 0 2 n − 1 − 1 A j f ( i ′ , j ′ ) + f ( c , 1 ) ∑ j = 2 n − 1 2 n − 1 A j f ( i ′ , j ′ ) =f(c,0)\sum\limits_{j=0}^{2^{n-1}-1}A_jf(i',j')+f(c,1)\sum\limits_{j=2^{n-1}}^{2^n-1}A_jf(i',j') =f(c,0)j=0∑2n−1−1Ajf(i′,j′)+f(c,1)j=2n−1∑2n−1Ajf(i′,j′)
递归求解即可
这样我们就求出了 F W T ( A ⊗ B ) FWT(A\otimes B) FWT(A⊗B)。接下来考虑它的逆变换,注意到 F W T FWT FWT 的过程可以看作是把 A A A 数组乘上 f f f 矩阵,那么逆变换只需要把 f f f 变成其对应的逆矩阵就好了。
也就是说, F W T FWT FWT 适用于优化卷积的条件是该位运算满足 f ( i , j ) f ( i , k ) = f ( i , j ⊕ k ) f(i,j)f(i,k)=f(i,j\oplus k) f(i,j)f(i,k)=f(i,j⊕k) 且 f f f 有逆矩阵
复杂度为 O ( n 2 n ) O(n2^n) O(n2n)
即 C k = ∑ i ∣ j = k A i × B j C_k=\sum\limits_{i|j=k} A_i\times B_j Ck=i∣j=k∑Ai×Bj
C = A ∣ B C=A|B C=A∣B, f ( i , j ) f ( i , k ) = f ( i , j ∣ k ) f(i,j)f(i,k)=f(i,j|k) f(i,j)f(i,k)=f(i,j∣k),注意到 j ∣ k j|k j∣k 是 i i i 的子集当且仅当 k k k 为 i i i 子集 且 j j j 为 i i i 子集
那么构造矩阵 f ( i , j ) = [ j ⊂ i ] f(i,j)=[j\subset i] f(i,j)=[j⊂i]
[ 1 0 1 1 ] \begin{bmatrix} 1 & 0 \\ 1 & 1 \end{bmatrix} [1101]
左继承,右加左
逆矩阵为
[ 1 0 − 1 1 ] \begin{bmatrix} 1 & 0 \\ -1 & 1 \end{bmatrix} [1−101]
左不变,右减左
即 C k = ∑ i & j = k A i × B j C_k=\sum\limits_{i\& j=k} A_i\times B_j Ck=i&j=k∑Ai×Bj
f ( i , j ) f ( i , k ) = f ( i , j & k ) f(i,j)f(i,k)=f(i,j\& k) f(i,j)f(i,k)=f(i,j&k), i i i 是 j & k j\&k j&k 的子集当且仅当 i i i 是 j j j 的子集 且 i i i 是 k k k 的子集
构造矩阵 f ( i , j ) = [ i ⊂ j ] f(i,j)=[i\subset j] f(i,j)=[i⊂j]
[ 1 1 0 1 ] \begin{bmatrix} 1 & 1 \\ 0 & 1 \end{bmatrix} [1011]
逆矩阵为
[ 1 − 1 0 1 ] \begin{bmatrix} 1 & -1 \\ 0 & 1 \end{bmatrix} [10−11]
f ( i , j ) f ( i , k ) = f ( i , j ˆ k ) f(i,j)f(i,k)=f(i,j\^\ k) f(i,j)f(i,k)=f(i,j ˆk)
需要一些观察,构造 f ( i , j ) = ( − 1 ) ∣ i & j ∣ f(i,j)=(-1)^{|i\& j|} f(i,j)=(−1)∣i&j∣,因为 ( − 1 ) ∣ i & j ∣ + ∣ i & k ∣ = ( − 1 ) ∣ i & ( j ˆ k ) ∣ (-1)^{|i\& j|+|i\& k|}=(-1)^{|i\& (j\^\ k)|} (−1)∣i&j∣+∣i&k∣=(−1)∣i&(j ˆk)∣,若 i i i 这一位为 0 0 0,两边都没有贡献;否则只有 j , k j,k j,k 不同才有贡献,符合 x o r xor xor 性质
构造:
[ 1 1 1 − 1 ] \begin{bmatrix} 1 & 1 \\ 1 & -1 \end{bmatrix} [111−1]
逆矩阵为
[ 1 2 − 1 2 1 2 1 2 ] \large\begin{bmatrix} \frac{1}{2} & -\frac{1}{2} \\ \frac{1}{2} & \frac{1}{2} \end{bmatrix} [2121−2121]
#include
using namespace std ;
typedef long long LL ;
const int N = 17 , mod = 998244353 ;
int n ;
LL a[1<<N] , b[1<<N] , A[1<<N] , B[1<<N] ;
void FWT_OR( LL *f )
{
for(int i = 1 ; i <= n ; i ++ ) {
int p = (1<<(i-1)) ;
for(int j = 0 ; j < (1<<n) ; j += (1<<i) ) {
for(int k = 0 ; k < p ; k ++ ) {
f[j+k+p] = ( f[j+k+p] + f[j+k] ) % mod ;
}
}
}
}
void IFWT_OR( LL *f )
{
for(int i = 1 ; i <= n ; i ++ ) {
int p = (1<<(i-1)) ;
for(int j = 0 ; j < (1<<n) ; j += (1<<i) ) {
for(int k = 0 ; k < p ; k ++ ) {
f[j+k+p] = ( f[j+k+p] - f[j+k] ) % mod ;
}
}
}
}
void FWT_AND( LL *f )
{
for(int i = 1 ; i <= n ; i ++ ) {
int p = (1<<(i-1)) ;
for(int j = 0 ; j < (1<<n) ; j += (1<<i) ) {
for(int k = 0 ; k < p ; k ++ ) {
f[j+k] = ( f[j+k] + f[j+k+p] ) % mod ;
}
}
}
}
void IFWT_AND( LL *f )
{
for(int i = 1 ; i <= n ; i ++ ) {
int p = (1<<(i-1)) ;
for(int j = 0 ; j < (1<<n) ; j += (1<<i) ) {
for(int k = 0 ; k < p ; k ++ ) {
f[j+k] = ( f[j+k] - f[j+k+p] ) % mod ;
}
}
}
}
void FWT_XOR( LL *f )
{
for(int i = 1 ; i <= n ; i ++ ) {
int p = (1<<(i-1)) ;
for(int j = 0 ; j < (1<<n) ; j += (1<<i) ) {
for(int k = 0 ; k < p ; k ++ ) {
LL a = f[j+k] , b = f[j+k+p] ;
f[j+k] = (a+b)%mod ;
f[j+k+p] = (a-b)%mod ;
}
}
}
}
const LL inv2 = (mod+1)/2 ;
void IFWT_XOR( LL *f )
{
for(int i = 1 ; i <= n ; i ++ ) {
int p = (1<<(i-1)) ;
for(int j = 0 ; j < (1<<n) ; j += (1<<i) ) {
for(int k = 0 ; k < p ; k ++ ) {
LL a = f[j+k] , b = f[j+k+p] ;
f[j+k] = (a+b)*inv2%mod ;
f[j+k+p] = (a-b)*inv2%mod ;
}
}
}
}
int main()
{
scanf("%d" , &n ) ;
for(int i = 0 ; i < (1<<n) ; i ++ ) scanf("%lld" , &A[i] ) ;
for(int i = 0 ; i < (1<<n) ; i ++ ) scanf("%lld" , &B[i] ) ;
memcpy(a,A,sizeof a) ; memcpy(b,B,sizeof b) ;
FWT_OR(a) ; FWT_OR(b) ;
for(int i = 0 ; i < (1<<n) ; i ++ ) a[i] = a[i]*b[i]%mod ;
IFWT_OR(a) ;
for(int i = 0 ; i < (1<<n) ; i ++ ) printf("%lld " , (a[i]+mod)%mod ) ;
printf("\n") ;
memcpy(a,A,sizeof a) ; memcpy(b,B,sizeof b) ;
FWT_AND(a) ; FWT_AND(b) ;
for(int i = 0 ; i < (1<<n) ; i ++ ) a[i] = a[i]*b[i]%mod ;
IFWT_AND(a) ;
for(int i = 0 ; i < (1<<n) ; i ++ ) printf("%lld " , (a[i]+mod)%mod ) ;
printf("\n") ;
memcpy(a,A,sizeof a) ; memcpy(b,B,sizeof b) ;
FWT_XOR(a) ; FWT_XOR(b) ;
for(int i = 0 ; i < (1<<n) ; i ++ ) a[i] = a[i]*b[i]%mod ;
IFWT_XOR(a) ;
for(int i = 0 ; i < (1<<n) ; i ++ ) printf("%lld " , (a[i]+mod)%mod ) ;
printf("\n") ;
return 0 ;
}
推导过程没什么区别,问题在于构造 f f f 矩阵
FWT and
定义 K K K 进制下 a n d and and 运算为 i & j = max ( i , j ) i \& j=\max(i,j) i&j=max(i,j),本质上是高维后缀和
转移矩阵: [ 1 1 1 . . . 1 0 1 1 . . . 1 0 0 1 . . . 1 ⋮ ⋮ ⋮ ⋱ ⋮ 0 0 0 . . . 1 ] \large\begin{bmatrix} 1 & 1 &1 &... &1 \\ 0 & 1 &1 &... &1 \\ 0 & 0 &1 &... &1 \\ \vdots &\vdots&\vdots&\ddots &\vdots \\ 0 & 0 & 0 & ... &1 \end{bmatrix} 100⋮0110⋮0111⋮0.........⋱...111⋮1
FWT or
i ∣ j = min ( i , j ) i | j=\min(i,j) i∣j=min(i,j),本质上是高维前缀和
转移矩阵: [ 1 0 0 . . . 0 1 1 0 . . . 0 1 1 1 . . . 0 ⋮ ⋮ ⋮ ⋱ ⋮ 1 1 1 . . . 1 ] \large\begin{bmatrix} 1 & 0 &0 &... &0 \\ 1 & 1 &0 &... &0 \\ 1 & 1 &1 &... &0 \\ \vdots &\vdots&\vdots&\ddots &\vdots \\ 1 & 1 & 1 & ... &1 \end{bmatrix} 111⋮1011⋮1001⋮1.........⋱...000⋮1
FWT xor
定义 i ⊕ j = ( i + j ) m o d K i\oplus j=(i+j)\mod K i⊕j=(i+j)modK,要求 f ( i , j ) f ( i , k ) = f ( i , ( j + k ) % K ) f(i,j)f(i,k)=f(i,(j+k)\% K) f(i,j)f(i,k)=f(i,(j+k)%K)
注意到单位根有类似的周期性质,发现只需令 f ( i , j ) = w K j f(i,j)=w_K^j f(i,j)=wKj 即可满足上式
但这样会导致矩阵每一行都一样,没有逆,所以我们再钦定一手 f ( i , j ) = w K i j f(i,j)=w_K^{ij} f(i,j)=wKij
那么矩阵: [ 1 1 1 . . . 1 1 w K 1 w K 2 . . . w K K − 1 1 w K 2 w K 4 . . . w K 2 ( K − 1 ) ⋮ ⋮ ⋮ ⋱ ⋮ 1 w K K − 1 w K ( K − 1 ) 2 . . . w K ( K − 1 ) ( K − 1 ) ] \begin{bmatrix} 1 & 1 &1 &... &1 \\ 1 & w_K^1 &w_K^2 &... &w_K^{K-1} \\ 1 & w_K^2 &w_K^4 &... &w_K^{2(K-1)} \\ \vdots &\vdots&\vdots&\ddots &\vdots \\ 1 & w_K^{K-1} &w_K^{(K-1)2} &... &w_K^{(K-1)(K-1)} \end{bmatrix} 111⋮11wK1wK2⋮wKK−11wK2wK4⋮wK(K−1)2.........⋱...1wKK−1wK2(K−1)⋮wK(K−1)(K−1)
这不就和我们在 FFT 中见到的矩阵一模一样吗?它就是 范德蒙德矩阵,其逆为:
1 K [ 1 1 1 . . . 1 1 1 / w K 1 1 / w K 2 . . . 1 / w K K − 1 1 1 / w K 2 1 / w K 4 . . . 1 / w K 2 ( K − 1 ) ⋮ ⋮ ⋮ ⋱ ⋮ 1 1 / w K K − 1 1 / w K ( K − 1 ) 2 . . . 1 / w K ( K − 1 ) ( K − 1 ) ] \frac{1}{K}\begin{bmatrix} 1 & 1 &1 &... &1 \\ 1 & 1/w_K^1 &1/w_K^2 &... &1/w_K^{K-1} \\ 1 & 1/w_K^2 &1/w_K^4 &... &1/w_K^{2(K-1)} \\ \vdots &\vdots&\vdots&\ddots &\vdots \\ 1 & 1/w_K^{K-1} &1/w_K^{(K-1)2} &... &1/w_K^{(K-1)(K-1)} \end{bmatrix} K1 111⋮111/wK11/wK2⋮1/wKK−111/wK21/wK4⋮1/wK(K−1)2.........⋱...11/wKK−11/wK2(K−1)⋮1/wK(K−1)(K−1)
(注:以下内容并非严谨的定义,只是笔者为了方便理解而使用的一些类比)
(多数是参考了这篇 blog)
一种比较简单的理解方式:集合幂级数就是以某个全集 U U U 的所有子集 2 U 2^U 2U 为定义域,以集合为自变量,映射到另一个域(oi 中通常研究的是对某个大质数取模后的整数域)的函数。把 U U U 的每个子集 S S S 带入函数,均有对应取值 f S f_S fS
我们考虑如何表示一个集合幂级数,首先给每个集合 S S S 对应的取值 f S f_S fS 后添加占位符 x S x^S xS,记作 f S x S f_Sx^S fSxS, 表示当且仅当 x = S x=S x=S 时,返回值为 f S f_S fS,否则为 0 0 0。也就是说,我们记 f = ∑ S ⊂ 2 U f S x S f=\sum\limits_{S\subset 2^U} f_Sx^S f=S⊂2U∑fSxS
接下来定义集合幂级数的运算
加法很好理解,直接把同类项系数相加即可,当然进行加法运算的两个集合幂级数需要保证全集相同
对于乘法,考虑集合幂级数 h = f ⋅ g = ( ∑ L ⊂ U f L x L ) ( ∑ R ⊂ U g R x R ) h=f\cdot g=(\sum\limits_{L\subset U}f_Lx^L)(\sum\limits_{R\subset U}g_Rx^R) h=f⋅g=(L⊂U∑fLxL)(R⊂U∑gRxR)
很自然的,我们希望这种运算也具有分配律,那么 h = ∑ L , R ( f L × g R ) x L ∗ R h=\sum\limits_{L,R}(f_L\times g_R)x^{L*R} h=L,R∑(fL×gR)xL∗R
其中第一个 × \times × 是我们熟知的整数域上的运算,直接把对应系数相乘;而 L ∗ R L*R L∗R 则是在 2 U 2^U 2U 上的二元运算,它决定了 集合之间的贡献方式,类型不唯一,常见的有 并卷积,对称差卷积,子集卷积,分别对应位运算中的 或卷积,异或卷积,子集卷积
由于前两种运算与上文的位运算卷积没有很大的区别,我们重点看第三种 —— 子集卷积
这种二元运算形式化的定义是: x L ⋅ x R = { 0 ( L ∩ R ≠ ∅ ) x L ∪ R ( L ∩ R = ∅ ) x^L\cdot x^R=\left\{\begin{matrix} 0 &(L\cap R≠\emptyset) \\ x^{L\cup R} &(L\cap R=\emptyset) \end{matrix}\right. xL⋅xR={0xL∪R(L∩R=∅)(L∩R=∅)
那么我们求的就是 h S = ∑ L ⊂ 2 U ∑ R ⊂ 2 U [ L ∪ R = S ] [ L ∩ R = ∅ ] f L g R h_S=\sum\limits_{L\subset 2^U}\sum\limits_{R\subset 2^U}[L\cup R=S][L\cap R=\emptyset]f_Lg_R hS=L⊂2U∑R⊂2U∑[L∪R=S][L∩R=∅]fLgR
暴力的子集枚举复杂度是 O ( 3 n ) O(3^n) O(3n),我们考虑更好的做法
注意力惊人的发现当 L ∪ R = S L\cup R=S L∪R=S 时, L ∩ R = ∅ L\cap R=\emptyset L∩R=∅ 等价于 ∣ L ∣ + ∣ R ∣ = ∣ S ∣ |L|+|R|=|S| ∣L∣+∣R∣=∣S∣,我们考虑设 F i , s = [ ∣ s ∣ = i ] f s F_{i,s}=[|s|=i]f_s Fi,s=[∣s∣=i]fs,同理有 G i , s , H i , s G_{i,s},H_{i,s} Gi,s,Hi,s
可以得到: H i = ∑ j + k = i F j ⊗ G k H_{i}=\sum\limits_{j+k=i}F_{j}\otimes G_k Hi=j+k=i∑Fj⊗Gk其中 ⊗ \otimes ⊗ 是集合并卷积,对两边同取 F W T FWT FWT 得到:
F W T ( H i ) s = ∑ j + k = i F W T ( F j ) s × F W T ( G k ) s FWT(H_i)_s=\sum\limits_{j+k=i}FWT(F_j)_s\times FWT(G_k)_s FWT(Hi)s=j+k=i∑FWT(Fj)s×FWT(Gk)s发现这就是普通的乘法卷积,对每个 s s s 暴力 n 2 n^2 n2 卷积即可,最后 I F W T IFWT IFWT 还原
复杂度是 O ( n 2 2 n ) O(n^22^n) O(n22n)
半在线子集卷积
实际题目中我们遇到的 d p dp dp 转移式更多是这样:
f s = ∑ t ⊂ s f t g s / t f_s=\sum\limits_{t\subset s}f_tg_{s/t} fs=t⊂s∑ftgs/t
称这种转移为半在线子集卷积
注意到我们做子集卷积时本身就有按 ∣ s ∣ |s| ∣s∣ 大小的顺序,直接从小往大做就行
P4221 [WC2018] 州区划分
基本是半在线子集卷积板子题,注意 需要微调转移式使得参与卷积的两部分独立
P11734 [集训队互测 2015] 胡策的统计
感觉又是很神秘的技巧,经常会遇到这种 为了确保顺序在转移时钦定枚举的子集 t t t 要包含 s s s 中最小的元素
这种怎么子集卷积优化呢?首先把钦定最小改成钦定最大方便一点,然后注意到 只有最高位相同的 f f f 才会互相转移,在外层枚举一个最高位 i d id id,接下来对 2 i d 2^{id} 2id 的部分跑正常的子集卷积,复杂度 ∑ i 2 2 i \sum i^22^i ∑i22i ,还是 O ( n 2 2 n ) O(n^22^n) O(n22n)
for(int id = 0 ; id < n ; id ++ ) {//最大值所在位
for(int i = 0 ; i <= id ; i ++ ) {
for(int s = 0 ; s < (1<<id) ; s ++ ) {
if( i == num[s] ) G[i][s] = pw2[E[s]] ;
else G[i][s] = 0 ;
F[i][s] = 0 ;
}
}
for(int i = 0 ; i <= id ; i ++ ) fwt(G[i],id) ;
for(int i = 0 ; i <= id ; i ++ ) {
if( i == 0 ) {
dp[(1<<id)] = 1 ;
for(int s = 0 ; s < (1<<id) ; s ++ ) F[i][s] = 1 ;
}
else {
for(int j = 0 ; j < i ; j ++ ) {
for(int s = 0 ; s < (1<<id) ; s ++ ) {
F[i][s] = ( F[i][s] + 1LL*F[j][s]*G[i-j][s] ) % mod ;
}
}
ifwt(F[i],id) ;
for(int s = 0 ; s < (1<<id) ; s ++ ) {
if( num[s]==i ) {
F[i][s] = ( pw2[E[s^(1<<id)]] - F[i][s] + mod ) % mod ;
dp[s^(1<<id)] = F[i][s] ;
}
}
fwt(F[i],id) ;
}
}
}
既然它也叫幂级数,那自然也有全家桶啦
设 f f f 是一个集合幂级数,定义 exp ( f ) = ∑ i ≥ 0 f i i ! \exp(f)=\sum\limits_{i\geq 0}\Large\frac{f^i}{i!} exp(f)=i≥0∑i!fi,为了避免不收敛我们规定 [ x ∅ ] f = 0 [x^{\emptyset}]f=0 [x∅]f=0
通常这里的乘法定义都是子集卷积,以此为例来说明一下做法:
设 g = exp ( f ) g=\exp(f) g=exp(f),同时定义它们的占位多项式为 F , G F,G F,G
g = ∑ i ≥ 0 f i i ! g=\sum\limits_{i\geq 0}\frac{f^i}{i!} g=i≥0∑i!fi
G j = ∑ i ≥ 0 , ∑ a k = j F a 1 ⊗ F a 2 . . . F a i i ! G_j=\sum\limits_{i\geq 0,\sum a_k=j}\frac{F_{a_1}\otimes F_{a_2}...F_{a_i}}{i!} Gj=i≥0,∑ak=j∑i!Fa1⊗Fa2...Fai
同取 F W T FWT FWT:
F W T ( G j ) s = ∑ i ≥ 0 , ∑ a k = j F W T ( F a 1 ) s × F W T ( F a 2 ) s . . . F W T ( F a i ) s i ! FWT(G_j)_s=\sum\limits_{i\geq 0,\sum a_k=j}\frac{FWT(F_{a_1})_s\times FWT(F_{a_2})_s...FWT(F_{a_i})_s}{i!} FWT(Gj)s=i≥0,∑ak=j∑i!FWT(Fa1)s×FWT(Fa2)s...FWT(Fai)s
容易发现此时固定一个 s s s 后,等式右边就是一个形式幂级数 exp \exp exp
难道我们还需要写 O ( n log n ) O(n\log n) O(nlogn) 的 NTT 吗?这很愚蠢啊, n n n 这么小考虑 n 2 n^2 n2 暴力
哎,怎么 n 2 n^2 n2 求 exp \exp exp 呢?
设 G ( x ) = exp ( F ( x ) ) G(x)=\exp(F(x)) G(x)=exp(F(x)),两边同时对 x x x 求导:
G ( x ) ′ = exp ( F ( x ) ) F ′ ( x ) G(x)'=\exp(F(x))F'(x) G(x)′=exp(F(x))F′(x)
G ( x ) ′ = G ( x ) F ′ ( x ) G(x)'=G(x)F'(x) G(x)′=G(x)F′(x)
展开:
∀ i ≥ 0 , [ x i ] G ′ = ∑ j ≤ i [ x j ] G × [ x i − j ] F ′ \forall i\geq 0,[x^i]G'=\sum\limits_{j\leq i}[x^j]G\times [x^{i-j}]F' ∀i≥0,[xi]G′=j≤i∑[xj]G×[xi−j]F′
( i + 1 ) [ x i + 1 ] G = ∑ j ≤ i [ x j ] G × ( i − j + 1 ) [ x i − j + 1 ] F (i+1)[x^{i+1}]G=\sum\limits_{j\leq i}[x^j]G\times (i-j+1)[x^{i-j+1}]F (i+1)[xi+1]G=j≤i∑[xj]G×(i−j+1)[xi−j+1]F
即:
∀ i ≥ 1 , [ x i ] G = 1 i ∑ j < i [ x j ] G × ( i − j ) [ x i − j ] F \forall i\geq 1,[x^{i}]G=\frac{1}{i}\sum\limits_{j< i}[x^j]G\times (i-j)[x^{i-j}]F ∀i≥1,[xi]G=i1j<i∑[xj]G×(i−j)[xi−j]F
显然 [ x 0 ] G = 1 [x^0]G=1 [x0]G=1,然后就可以递推啦,复杂度就是 O ( n 2 ) O(n^2) O(n2)
接上文,如果把 i i i 这一维看成行, s s s 这一维看成列,我们做的操作实际上是 先对每一行 f w t fwt fwt,再对每一列做 exp \exp exp,最后再对每行 i f w t ifwt ifwt 回来
for(int i = 0 ; i <= m ; i ++ ) {//每行fmt
fmt(F[i]) ;
}
for(int s = 0 ; s < (1<<m) ; s ++ ) {//对每列做 m^2 exp
G[0][s] = 1 ;//规定,否则会出问题
for(int i = 1 ; i <= m ; i ++ ) {
for(int j = 0 ; j < i ; j ++ ) {
G[i][s] = ( G[i][s] + G[j][s]*F[i-j][s]%mod*(i-j) ) % mod ;
}
G[i][s] = G[i][s]*iv[i]%mod ;
}
}
for(int i = 0 ; i <= m ; i ++ ) {
ifmt(G[i]) ;
}
复杂度还是 O ( n 2 2 n ) O(n^22^n) O(n22n)
例题 P6570 [NOI Online #3 提高组] 优秀子序列
推导没有很大区别
设 f f f 为一集合幂级数,定义 ln ( f + 1 ) = ∑ i ≥ 1 ( − 1 ) i − 1 f i i ! \ln (f+1)=\sum\limits_{i\geq 1}(-1)^{i-1}\Large\frac{f^i}{i!} ln(f+1)=i≥1∑(−1)i−1i!fi,还是要求 [ x ∅ ] f = 0 [x^{\emptyset}]f=0 [x∅]f=0(这里 + 1 +1 +1 指的是集合幂级数的单位元,这是为了让它在 0 0 0 处能展开)
设 g = ln ( f + 1 ) g=\ln (f+1) g=ln(f+1),同理得到: F W T ( G j ) s = ∑ i ≥ 0 , ∑ a k = j ( − 1 ) i − 1 F W T ( F a 1 ) s × F W T ( F a 2 ) s . . . F W T ( F a i ) s i ! FWT(G_j)_s=\sum\limits_{i\geq 0,\sum a_k=j}(-1)^{i-1}\frac{FWT(F_{a_1})_s\times FWT(F_{a_2})_s...FWT(F_{a_i})_s}{i!} FWT(Gj)s=i≥0,∑ak=j∑(−1)i−1i!FWT(Fa1)s×FWT(Fa2)s...FWT(Fai)s
还是考虑 n 2 n^2 n2 求 ln \ln ln
设 G = ln F G=\ln F G=lnF,两边求导得:
G ′ = F ′ F G'=\frac{F'}{F} G′=FF′
F ′ = F G ′ F'=FG' F′=FG′
∀ i ≥ 0 , [ x i ] F ′ = ∑ j ≤ i [ x j ] F × [ x i − j ] G ′ \forall i\geq 0,[x^i]F'=\sum_{j\leq i}[x^j]F\times [x^{i-j}]G' ∀i≥0,[xi]F′=j≤i∑[xj]F×[xi−j]G′
( i + 1 ) [ x i + 1 ] F = ∑ j ≤ i [ x j ] F × ( i − j + 1 ) [ x i − j + 1 ] G (i+1)[x^{i+1}]F=\sum_{j\leq i}[x^j]F\times (i-j+1)[x^{i-j+1}]G (i+1)[xi+1]F=j≤i∑[xj]F×(i−j+1)[xi−j+1]G
∀ i ≥ 1 , [ x i ] F = 1 i ∑ j < i [ x j ] F × ( i − j ) [ x i − j ] G \forall i\geq1,[x^{i}]F=\frac{1}{i}\sum_{j< i}[x^j]F\times (i-j)[x^{i-j}]G ∀i≥1,[xi]F=i1j<i∑[xj]F×(i−j)[xi−j]G
[ x i ] F = 1 i ∑ 1 ≤ j ≤ i [ x i − j ] F × j [ x j ] G [x^{i}]F=\frac{1}{i}\sum_{1\leq j\leq i}[x^{i-j}]F\times j[x^{j}]G [xi]F=i11≤j≤i∑[xi−j]F×j[xj]G
取出 j = i j=i j=i 这一项
[ x i ] F = 1 i ∑ 1 ≤ j < i [ x i − j ] F × j [ x j ] G + [ x 0 ] F × [ x i ] G [x^{i}]F=\frac{1}{i}\sum_{1\leq j< i}[x^{i-j}]F\times j[x^{j}]G+[x^0]F\times [x^i]G [xi]F=i11≤j<i∑[xi−j]F×j[xj]G+[x0]F×[xi]G
注意到 ln F \ln F lnF 是良定义的当且仅当 [ x 0 ] F = 1 [x^0]F=1 [x0]F=1,那么我们直接就能得到:
[ x i ] G = [ x i ] F − 1 i ∑ 1 ≤ j < i [ x i − j ] F × j [ x j ] G [x^i]G=[x^i]F-\frac{1}{i}\sum\limits_{1\leq j[xi]G=[xi]F−i11≤j<i∑[xi−j]F×j[xj]G
这就可以愉快 n 2 n^2 n2 递推了
for(int s = 0 ; s < (1<<n) ; s ++ ) {// n^2 ln
G[0][s] = 0 ;
for(int i = 1 ; i <= n ; i ++ ) {
for(int j = 1 ; j < i ; j ++ ) {
G[i][s] = ( G[i][s] + 1LL*j*G[j][s]%mod*F[i-j][s] ) % mod ;
}
G[i][s] = ( F[i][s] - G[i][s]*inv[i] ) % mod ;
}
}
注意这里求的是 ln ( F + 1 ) \ln(F+1) ln(F+1)
设 f f f 为一集合幂级数,定义它的逆为使得 f ⊗ g = ϵ f\otimes g=\epsilon f⊗g=ϵ 的 g g g( ϵ \epsilon ϵ 为集合幂级数单位元)
还是先对每行 f m t fmt fmt,然后每列求逆,最后 i f m t ifmt ifmt
n 2 n^2 n2 求逆:
假设已知 F F F,求它的逆 G G G,我们设 f , g f,g f,g 表示它们的各项系数,有:
∑ i = 0 n f i g n − i = [ n = 0 ] \sum\limits_{i=0}^nf_ig_{n-i}=[n=0] i=0∑nfign−i=[n=0]
得到: g i = { 1 f 0 i = 0 − 1 f 0 ∑ j < i g j f i − j i ≥ 1 g_i=\left\{\begin{matrix} \large\frac{1}{f_0} & i=0\\ -\frac{1}{f_0}\sum\limits_{jgi=⎩ ⎨ ⎧f01−f01j<i∑gjfi−ji=0i≥1
例题:P11734 [集训队互测 2015] 胡策的统计,略微卡常
#154. 集合划分计数
设 F s F_s Fs 表示 s s s 有多少个,不难发现求的就是 [ x U ] ∑ i ≤ k F i i ! [x^{U}]\Large\sum\limits_{i\leq k}\frac{F^i}{i!} [xU]i≤k∑i!Fi,乘法还是子集卷积
通过上面的几种经典操作,我们可以总结出求这种式子的一般思路:
重点在于第二步。设 G = ∑ i ≤ k F i i ! G=\large\sum\limits_{i\leq k}\frac{F^i}{i!} G=i≤k∑i!Fi,两边求导,得:
G ′ = F ′ ∑ i ≤ k − 1 F i i ! G'=F'\sum_{i\leq k-1}\frac{F^i}{i!} G′=F′i≤k−1∑i!Fi
G ′ = F ′ ∑ i ≤ k − 1 F i i ! G'=F'\sum_{i\leq k-1}\frac{F^i}{i!} G′=F′i≤k−1∑i!Fi
G ′ = F ′ ( G − F k k ! ) G'=F'(G-\frac{F^k}{k!}) G′=F′(G−k!Fk)
这样只需要求一个 F k F^k Fk 就能递推了
求这个一般的想法是直接 F k = exp ( k ln F ) F^k=\exp(k\ln F) Fk=exp(klnF),但需要注意这个做法只有当 F F F 满足 [ x ∅ ] F = 1 [x^\emptyset]F=1 [x∅]F=1 才成立,否则会导致 ln \ln ln 未定义
那还是借助求导,找递推式的思路,设 G = F k G=F^k G=Fk,同求导:
G ′ = k F k − 1 F ′ G'=kF^{k-1}F' G′=kFk−1F′
配凑一下,两边同乘上 F F F:
G ′ F = k F k F ′ G'F=kF^{k}F' G′F=kFkF′
G ′ F = k G F ′ G'F=kGF' G′F=kGF′
又可以递推啦
写出来式子:
g i = k ∑ i = 0 n ( i + 1 ) f i + 1 g n − i − ∑ i = 0 n − 1 ( i + 1 ) g i + 1 f n − i i f 0 g_i=\frac{k\sum\limits_{i=0}^n(i+1)f_{i+1}g_{n-i}-\sum\limits_{i=0}^{n-1}(i+1)g_{i+1}f_{n-i}}{if_0} gi=if0ki=0∑n(i+1)fi+1gn−i−i=0∑n−1(i+1)gi+1fn−i
然后就发现,我 f 0 = 0 f_0=0 f0=0 还是做不了啊 ?!!
哎,别急,形式幂级数的运算肯定是更好做的,类比 这个题 中的技巧,我们找到 f f f 低位第一个非 0 0 0 的位置 p p p,以这个位置作为起点,同理 g g g 以 K ⋅ p K\cdot p K⋅p 作为起点进行递推就 ok 啦
#include
using namespace std ;
typedef long long LL ;
const int N = 21 , mod = 998244353 ;
LL ksm( LL a , LL b )
{
LL res = 1 , t = a % mod ;
while( b ) {
if( b&1 ) res = res * t % mod ;
b = b >> 1 ;
t = t * t % mod ;
}
return res ;
}
int n , m , K , x , num[1<<N] ;
LL F[N+1][1<<N] , G[N+1][1<<N] , inv[N+1] ;
void fmt( LL *f )
{
for(int i = 0 ; i < n ; i ++ ) {
for(int s = 0 ; s < (1<<n) ; s ++ ) {
if( s&(1<<i) ) {
f[s] = ( f[s] + f[s^(1<<i)] ) % mod ;
}
}
}
}
void ifmt( LL *f )
{
for(int i = 0 ; i < n ; i ++ ) {
for(int s = 0 ; s < (1<<n) ; s ++ ) {
if( s&(1<<i) ) {
f[s] = ( f[s] - f[s^(1<<i)] ) % mod ;
}
}
}
}
void poly_pow( LL *f , int K , LL *g )
{
int p = 0 ;
while( !f[p] && p <= n ) p ++ ;
if( p*K > n ) return ;
LL *nf = f+p , *ng = g+K*p , inv0 = ksm(nf[0],mod-2) ;
int nn = n-K*p ;
ng[0] = ksm(nf[0],K) ;
for(int i = 1 ; i <= nn ; i ++ ) {
for(int j = 0 ; j < i ; j ++ ) {
ng[i] = ( ng[i] + (j+1)*nf[j+1]%mod*ng[i-1-j] ) % mod ;
}
ng[i] = ng[i]*K%mod ;
for(int j = 0 ; j < i-1 ; j ++ ) {
ng[i] = ( ng[i] - (j+1)*ng[j+1]%mod*nf[i-1-j] ) % mod ;
}
ng[i] = ng[i]*inv[i]%mod*inv0%mod ;
}
}
int main()
{
scanf("%d%d%d" , &n , &m , &K ) ;
LL ifc = 1 ;
for(int i = 1 ; i <= K ; i ++ ) ifc = ifc*i%mod ;
ifc = ksm(ifc,mod-2) ;
for(int s = 0 ; s < (1<<n) ; s ++ ) num[s] = num[s>>1]+(s&1) ;
for(int i = 1 ; i <= m ; i ++ ) {
scanf("%d" , &x ) ;
F[num[x]][x] ++ ;
}
for(int i = 1 ; i <= n ; i ++ ) fmt(F[i]) , inv[i] = ksm(i,mod-2) ;
for(int s = 0 ; s < (1<<n) ; s ++ ) {
LL H[N+1] = {} , T[N+1] = {} ;
for(int i = 0 ; i <= n ; i ++ ) {
H[i] = F[i][s] ;
}
poly_pow(H,K,T) ;
for(int i = 0 ; i <= n ; i ++ ) T[i] = T[i]*ifc%mod ;
G[0][s] = 1 ;
for(int i = 1 ; i <= n ; i ++ ) {
for(int j = 0 ; j < i ; j ++ ) {
G[i][s] = ( G[i][s] + (G[j][s]-T[j])*(i-j)%mod*F[i-j][s] ) % mod ;
}
G[i][s] = G[i][s]*inv[i]%mod ;
}
}
ifmt(G[n]) ;
printf("%lld\n" , (G[n][(1<<n)-1]+mod)%mod ) ;
return 0 ;
}
(你可以说它没考过,但你不能否认它确实很有用,能做好多以前不敢想的东西)
[AGC034F] RNG and XOR
据说