zoukankan      html  css  js  c++  java
  • 卷与二进制学习总结

    卷与二进制学习总结

    有些人啊,总是把什么前缀和,什么差分啊,取一个高大上的名字“FWT”,就搞不懂有什么意义。

    以上为玩笑话。

    基础

    问题

    给定长度为 (2^w) 下标从 (0sim 2^w-1) 的两个数组 (A,B) ,分别求满足下列条件的数组 (C)

    [C_n=sum_{iland j=n}A_iB_j ]

    [C_n=sum_{ilor j=n}A_iB_j ]

    [C_n=sum_{ioplus j=n}A_iB_j ]

    [C_n=sum_{iland j=0,ilor j=n}A_iB_j ]

    更一般的点值表示?

    要求某一个卷积 (F*G) ,如果可以构造出一个输入为数组(函数)输出为数组(函数)的一个函数 (operatorname{Trans}()) ,并且 (operatorname{Trans}(F))(F) 的转化比较容易实现,并且 (operatorname{Trans}(F*G)_i=operatorname{Trans}(F)_i imes operatorname{Trans}(G)_i) ,那么就可以比较容易地求出 (F*G) 了。

    认为 (F o operatorname{Trans}(F)) 为正变换, (operatorname{Trans}(F) o F) 为逆变换。

    与卷积

    [C_n=sum_{iland j=n}A_iB_j ]

    考虑构造上面说的这样一个函数 (operatorname{Trans}()) ,假设这个函数为 (operatorname{Trans}(H)_i=sum_{j}f(j,i)H_j) ,那么:

    [operatorname{Trans}(C)_n=operatorname{Trans}(A*B)_n\ =operatorname{Trans}(A)_n imes operatorname{Trans}(B)_n\ =sum_{i}f(i,n)A_isum_{j}f(j,n)B_j\ =sum_{i,j}f(i,n)f(j,n)A_iB_j ]

    又由于有:

    [operatorname{Trans}(C)_n=sum_{k}f(k,n)C_k\ =sum_{k}f(k,n)sum_{iland j=k}A_iB_j\ =sum_{i,j}f(iland j,n)A_iB_j ]

    所以我们可以得到:

    [f(iland j,n)=f(i,n)f(j,n) ]

    相当于我们要构造的 (f()) 满足如上条件,并且 (operatorname{Trans}(H))(H) 之间的转化需要比较容易。

    怎么构造呢?这就要靠玄学了

    可以构造出这样的一个 (f())

    [f(i,n)=[iland n=n] ]

    (就是说 (f(i,n)=1) 当且仅当 (n)(i) 的子集)

    不难发现这个显然是满足 (f(iland j,n)=f(i,n)f(j,n)) 这个条件的,并且 (operatorname{Trans}(H))(H) 之间的转化也及其容易,就仅仅只是后缀和和差分罢了( (operatorname{Trans}(H))(H) 的后缀和)。

    时间复杂度为 (mathcal O(2^ww))

    参考代码:

    void FWT_and(int*F,int n,int rv){
    	if(!rv){
    		for(int mid=1;mid<n;mid<<=1){
    			for(int i=0;i<n;++i)if(i&mid){
    				F[i^mid]=mo(F[i^mid]+F[i]);
    			}
    		}
    	}
    	else{
    		for(int mid=1;mid<n;mid<<=1){
    			for(int i=0;i<n;++i)if(i&mid){
    				F[i^mid]=mo(mod-F[i]+F[i^mid]);
    			}
    		}
    	}
    }
    

    或卷积

    [C_n=sum_{ilor j=n}A_iB_j ]

    类似地,可以得到要构造的 (f()) 需满足:

    [f(ilor j,n)=f(i,n)f(j,n) ]

    类似地,构造:

    [f(i,n)=[iland n=i] ]

    (就是说 (f(i,n)=1) 当且仅当 (i)(n) 的子集)

    (operatorname{Trans}(H))(H) 的前缀和。

    时间复杂度为 (mathcal O(2^ww))

    参考代码:

    void FWT_or (int*F,int n,int rv){
    	if(!rv){
    		for(int mid=1;mid<n;mid<<=1){
    			for(int i=0;i<n;++i)if(i&mid){
    				F[i]=mo(F[i]+F[i^mid]);
    			}
    		}
    	}
    	else{
    		for(int mid=1;mid<n;mid<<=1){
    			for(int i=0;i<n;++i)if(i&mid){
    				F[i]=mo(mod-F[i^mid]+F[i]);
    			}
    		}
    	}
    }
    

    异或卷积

    [C_n=sum_{ioplus j=n}A_iB_j ]

    类似地,可以得到要构造的 (f()) 需满足:

    [f(ioplus j,n)=f(i,n)f(j,n) ]

    好像构造类似不了了

    (operatorname{bitcnt}(i)) 表示 (i)(1) 的个数,可以构造:

    [f(i,n)=(-1)^{operatorname{bitcnt}(iland n)} ]

    可以发现,这个式子满足上面所说的条件,然后 (operatorname{Trans}(H))(H) 如何转化呢?

    转化方法和前缀和差分是类似的,先考虑从 (H) 变成 (operatorname{Trans}(H)) ,依次考虑每一个维度 (i) ,然后枚举状态 (sta_0,sta_1) 满足 (sta_0+i=sta_1) ,设 (l=H_{sta_0},r=H_{sta_1}) ,那么令 (H_{sta_0}gets l+r,H_{sta_1}gets l-r) ;从 (operatorname{Trans}(H)) 变成 (H) 就是逆过程,相当于是已知 (l+r)(l-r)(l,r) ,那么 (l=frac{1}{2}((l+r)+(l-r)),r=frac{1}{2}((l+r)-(l-r))) ,也就是原过程求出来之后再额外乘以 (frac{1}{2})

    时间复杂度为 (mathcal O(2^ww))

    参考代码:

    void FWT_xor(int*F,int n,int rv){
    	for(int mid=1;mid<n;mid<<=1){
    		for(int i=0;i<n;++i)if(i&mid){
    			int l=(i^mid),r=i,L=F[l],R=F[r];
    			F[l]=mo(L+R),F[r]=mo(mod-R+L);
    			if(rv)
    				F[l]=1ll*F[l]*inv2%mod,
    				F[r]=1ll*F[r]*inv2%mod;
    		}
    	}
    }
    

    扩展到 (k) 维的与/或/异或卷积

    如果看懂了上面所说的, FWT 扩展到 (k) 维其实还是挺简单的,依次构造的 (f()) 需要满足:

    [f(min(i,j),n)=f(i,n)f(j,n) ]

    [f(max(i,j),n)=f(i,n)f(j,n) ]

    [f((i+j)mod k,n)=f(i,n)f(j,n) ]

    前两个还是前缀和与差分,最后面的那个就是长度为 (k) 的循环卷积,暴力或者 Bluestein's Algorithm 都可以。

    继续扩展

    对于任意的卷积( (operatorname{op}_1,operatorname{op}_2) 是运算符,其中 (operatorname{op}_2) 需要满足分配律):

    [C_n=sum_{ioperatorname{op}_1j=n}A_ioperatorname{op}_2B_j ]

    构造出来的 (f()) 需要满足的条件:

    [f(ioperatorname{op}_1j,n)=f(i,n)operatorname{op}_2f(j,n) ]

    如果多维度的每个维度的卷积定义不一样,仍然一个维度一个维度地做就行了。

    子集卷积

    [C_n=sum_{iland j=0,ilor j=n}A_iB_j ]

    这个式子可以转化为:

    [C_n=sum_{operatorname{bitcnt}(i)+operatorname{bitcnt}(j)=operatorname{bitcnt}(n),ilor j=n}A_iB_j ]

    考虑分层做卷积,将 (operatorname{bitcnt}(i)) 相等的作为一层,把 (A,B) 分成 (w+1) 层,每层都做一个“或正变换”,然后 (A) 的第 (i) 层和 (B) 的第 (j) 层对应点乘,加到 (C) 的第 (i+j) 层里面,最后再把 (C) 的每一层都“或逆变换”回去, (C_n) 的实际值就是 (C) 的第 (operatorname{bitcnt}(n)) 层里面 (n) 这个位置的值。

    时间复杂度为 (mathcal O(2^ww^2))

    应用

    集合幂级数

    在 OI 中,一个二进制数一般是用来表示一个集合的,不妨认为全集 (U={0,1,dots,w-1}) ,方便起见记 (2^w) 表示 (U) 的所有子集组成的集合,类似形式幂级数,我们可以定义集合幂级数:

    [F(x)=sum_{ssube 2^w}f_sx^s ]

    集合幂级数的加减就是对应位置直接加减,乘法的话就是上面所讲的四种卷积,除法等一系列高级运算在“与或异或卷积”中由于转化为了点乘的形式,直接在对应位置进行即可(比如除法就是对应位置做除法(正变换过后如果有 0 那么没有逆元),开根就是对应位置开根等),但是在“子集卷积”中有点不太一样,这里另作讨论。

    与或异或卷积

    一点记号

    为了方便起见,用 (operatorname{FWT}()) 表示正变换, (operatorname{IFWT}()) 表示逆变换,用 (igoplus) 表示异或卷积,如果 (prod) 后面接的是 (operatorname{FWT}()) 过后的式子这个 (prod) 指的是对应位置点乘。

    注意到变化过程中只用了加减操作,所以有:

    [operatorname{FWT}(A)+operatorname{FWT}(B)=operatorname{FWT}(A+B) ]

    [operatorname{IFWT}(A)+operatorname{IFWT}(B)=operatorname{IFWT}(A+B) ]

    洛谷P5406 [THUPC2019]找树

    直接暴力统计边权异或和为某个值的方案数,使用异或卷积,我们要求的就是这个:

    [sum_{T}x^{oplus_{ein T}v_e}=sum_{T}igoplus_{ein T}x^{v_e} =operatorname{IFWT}(sum_{T}prod_{ein T}operatorname{FWT}(x^{v_e})) ]

    恰好矩阵树定理求的就是若干边权的乘积,所以可以用矩阵树来求上面的这个东西,时间复杂度 (mathcal O(n^32^w+m2^w)) (玄学跑过)。

    洛谷P5387 [Cnoi2019]人形演舞

    先求出 (1)(m) 的每个数字的 (operatorname{SG}) 函数,问题就变成了询问依次选出来的 (|V|) 个数异或起来不为 (0) 的方案数,后半部分就可以用异或卷积然后取 (|V|) 次方很快求出来。

    如何求每个数字的 (operatorname{SG}) 函数?不难发现 (x) 可以变成 (y) 当且仅当 (0le y<x,operatorname{highbit}(y)in x) ,可以先列举几个找找规律,首先 (operatorname{SG}(2^k)=1,operatorname{SG}(2^k+1)=2,...) ,可以发现由于 (x) 可以变成任何的一个 (y) 满足 (y<x) 并且 (operatorname{highbit}(y)=operatorname{highbit}(x)) ,所以可以猜想 (operatorname{SG}(x)=x-2^{operatorname{highbit}(x)}+1) ,归纳法很容易就可以证明了。

    UOJ310 【UNR #2】黎明前的巧克力

    考虑 Evan 和 Lyra 选择的巧克力的并集 (S)(S) 中的所有数异或起来必然等于 (0) ,并且 Evan 可以选择 (S) 的任意一个子集,所以可以认为 (S) 对答案的贡献就是 (2^{|S|}) ,于是问题就变成了求所有异或为 (0) 的集合的贡献和,也就是求:

    [[x^0]igoplus_{i=1}^n(2x^{a_i}+x^0)=[x^0]operatorname{IFWT}(prod_{i=1}^noperatorname{FWT}(2x^{a_i}+x^0)) ]

    暴力 (operatorname{FWT}()) 再点乘起来时间复杂度无法接受,考虑异或卷积实际上求的是什么:

    [[x^s]operatorname{FWT}(2x^{a_i}+x^0)=2 imes (-1)^{operatorname{bitcnt}(sland a_i)}+1 imes (-1)^{operatorname{bitcnt}(sland 0)}=2 imes (-1)^{operatorname{bitcnt}(sland a_i)}+1 ]

    对于不同的 (s)([x^s]operatorname{FWT}(2x^{a_i}+x^0)) 只有两种可能的取值,即 (3) 或者 (-1) ,所以全部点乘起来得到的某个 (x^s) 前面的系数必然可以表示为 (3^t(-1)^{n-t}) 次方的形式:

    [[x^s]prod_{i=1}^noperatorname{FWT}(2x^{a_i}+x^0)=3^t(-1)^{n-t} ]

    要求某一位的系数也就是要求出 (t) ,考虑来点骚操作:

    [[x^s]sum_{i=1}^noperatorname{FWT}(2x^{a_i}+x^0)=3t-(n-t)=4t-n ]

    [[x^s]operatorname{FWT}(nx^0+2sum_{i=1}^nx^{a_i})=4t-n ]

    由此一遍 (operatorname{FWT}()) 就可以求出每一位的系数,然后再一遍 (operatorname{IFWT}()) 回去就行了。

    一道题目

    给定 (n,k,m) 以及序列 (a_i(0le i< k)) 和二维序列 (P_{i,j}(0le i< n,0le j< k,P_{i,j}in[0,2^m-1])) ,对于所有的 (sin[0,2^m-1]) ,求:

    [sum_{|c|=n,0le c_i< k}[oplus_{i=0}^{n-1}P_{i,c_i}=s]prod_{i=0}^{n-1}a_{c_i} mod 10^9+7 ]

    (1le nle 10^6,1le m,k,m+kle 20)

    题目要求的也就是:

    [operatorname{IFWT}(prod_{i=0}^{n-1}operatorname{FWT}(sum_{j=0}^{k-1}a_jx^{P_{i,j}})) ]

    由上一题的启发,可以得到 (operatorname{FWT}(sum_{j=0}^{k-1}a_jx^{P_{i,j}})) 某一位的取值只有 (2^k) 种(即考虑每个 (a_j) 前面乘以的系数是 (1) 还是 (-1) ),可以用一个二进制数 (operatorname{stk}) 表示这 (2^k) 种不同的取值:

    [operatorname{val(stk)}=sum_{j=0}^{k-1}a_j imesegin{cases}-1& operatorname{stk}_j=0\ 1 & operatorname{stk}_j=1 end{cases} ]

    不妨设 (operatorname{cnt(stm,stk)}) 表示 (operatorname{IFWT}()) 里面的那个式子的第 (operatorname{stm}) 位有多少个 (a_j) 状况为 (operatorname{stk}) 的,那么有:

    [[x^{operatorname{stm}}]prod_{i=0}^{n-1}operatorname{FWT}(sum_{j=0}^{k-1}a_jx^{P_{i,j}})=prod_{operatorname{stk}}operatorname{val(stk)^{cnt(stm,stk)}} ]

    [[x^{operatorname{stm}}]sum_{i=0}^{n-1}operatorname{FWT}(sum_{j=0}^{k-1}a_jx^{P_{i,j}})=sum_{operatorname{stk}}operatorname{val(stk) imes cnt(stm,stk)} ]

    [[x^{operatorname{stm}}]operatorname{FWT}(sum_{i=0}^{n-1}sum_{j=0}^{k-1}a_jx^{P_{i,j}})=sum_{operatorname{stk}}operatorname{val(stk) imes cnt(stm,stk)} ]

    我们的目的是对于所有的 (operatorname{stm,stk}) 要求出 (operatorname{cnt(stm,stk)}) ,考虑用解方程的方式,枚举长度为 (k) 的二进制数 (operatorname{sta}) ,并且构造序列 (A)

    [A_j=egin{cases}-1& operatorname{sta}_j=0\1& operatorname{sta}_j=1end{cases} ]

    可以得到:

    [sum_{j=0}^{k-1}A_j imesegin{cases}-1& operatorname{stk}_j=0\ 1 & operatorname{stk}_j=1 end{cases}=k-2operatorname{bitcnt(staoplus stk)} ]

    令:

    [H(operatorname{stm,sta})=[x^{operatorname{stm}}]operatorname{FWT}(sum_{i=0}^{n-1}sum_{j=0}^{k-1}A_jx^{P_{i,j}}) ]

    那么:

    [H(operatorname{stm,sta})=sum_{operatorname{stk}}(k-2operatorname{bitcnt(staoplus stk)) imes cnt(stm,stk)} ]

    上面是一个异或卷积的形式,知道前两项求最后一项可以用异或卷积里面的除法实现,总的时间复杂度是 (mathcal O(nk+2^{m+k}(m+k)))

    子集卷积

    洛谷P6846 [CEOI2019] Amusement Park

    先考虑求 DAG 方案数,关键是如何枚举 DAG 才能不重不漏,可以设 (f_{S}) 表示点集 (S) 形成 DAG 的方案数,考虑到如果 (S) 是 DAG ,那么必然会存在入度为 (0) 的点,但是将入度为 (0) 的点删掉并不能保证剩下的入度为 (0) 的点和这些点之间有连边,不过可以容斥求这个东西,注意到有 (sum_{i=1}^n(-1)^{i-1}{nchoose i}=[n>0]) ,所以可以写出转移式子:

    [f_S=sum_{Tsube S,T e varnothing}(-1)^{|T|-1}[forall (u,v)in E,u otin Tlor v otin T]f_{S/T} ]

    注意到子集卷积的具体实现过程中是分层做卷积的,可以边子集卷积边 dp 。

    时间复杂度 (mathcal O(2^nn^2))

    子集卷积的高级操作

    由于我们在实现“子集卷积”的时候是通过一种比较复杂的方式实现的,并不像“与或异或卷积”一样方便地正变换后对应位置做乘除即可,在“子集卷积”的具体实现过程中,可以理解为我们还给每个对应位置加了一维(上面所说的“层数”),而两个对应位置卷起来同时它们的层数还会对应相加(第 (i) 层的位置 (s) 和第 (j) 层的位置 (s) 乘起来会贡献到第 (i+j) 的位置 (s) ),但是可以发现此时不同位置之间仍然是互不相关的,所以可以把每个位置分开考虑,某个位置的操作恰好就是一般我们说的多项式(形式幂级数)的卷积,所以每个位置依次按照形式幂级数的方式进行操作即可。

    具体来说,如果要对“子集卷积”进行操作,先分层,然后每层都“或卷积”正变换过去,接着每个位置分开考虑,用形式幂级数的方式堆每个位置进行操作,然后再每层“或卷积”逆变换回去,再将所有层合并即可。

    还有需要注意的一点就是,一开始的变换导致了时间复杂度的下界为 (mathcal O(2^ww^2)) ,不能再小了,而在对形式幂级数进行操作的时候我们是在对 (2^w) 个长度为 (w+1) 的形式幂级数进行操作的,所以把这个操作用 FFT 之类的优化成 (mathcal O(wlog_2w)) 是没有意义的,所以此时的操作建议使用 (mathcal O(w^2)) 的暴力递推(暴力递推的一些操作可以看这里)。

    LOJ154 集合划分计数

    一个子集卷积的高级操作的例子。

    (F=sum_{i=0}^{m-1}x^{s_i}) ,注意到 (1le s_i) ,所以 (F_0=0) ,题目要求的就是这个东西:

    [[x^{2^n-1}]sum_{i=0}^kfrac{F^i}{i!} ]

    推式子:

    [H=sum_{i=0}^kfrac{F^i}{i!} ]

    [H'=sum_{i=1}^{k}frac{iF^{i-1}}{i!}F' ]

    [H'=F'sum_{i=1}^kfrac{F^{i-1}}{(i-1)!} ]

    [H'=F'sum_{i=0}^{k-1}frac{F^i}{i!} ]

    [H'=F'(H-frac{F^k}{k!}) ]

    [nH_n=sum_{i+j=n}iF_i(H_j-(frac{F^k}{k!})_j) ]

    其中 (H_0=1)(F^k) 直接多项式快速幂。

  • 相关阅读:
    数据结构 图的接口定义与实现分析(超详细注解)
    数据结构-优先队列 接口定义与实现分析
    什么是梯度?
    javascript当中火狐的firebug如何单步调试程序?
    给出一个javascript的Helloworld例子
    lvs dr 模型配置详解
    数据库连接池
    JDBC事务管理
    JDBC工具类:JDBCUtils
    详解JDBC对象
  • 原文地址:https://www.cnblogs.com/lsq147/p/14359274.html
Copyright © 2011-2022 走看看