zoukankan      html  css  js  c++  java
  • 反演与集合幂级数

    VFK的PPT堪称经典,迄今为止看过的最好的一篇,真的是体现出数学精妙。

    主要就是各个方面的融汇贯通,涉及的知识有:复数,莫比乌斯反演,矩阵变换,微积分初步等。

    直接进入正题。

    转载请注明出处:http://www.cnblogs.com/HocRiser/p/8886806.html

    目录:

      一.反演定义

      二.二项式反演

      三.莫比乌斯反演

      四.集合幂级数

      五.集合并卷积与集合对称差卷积

      六.离散傅立叶变换(Discrete Fourier Transform)

      七.快速沃尔什变换(Fast Walsh-Hadamard Transform)

      八.快速莫比乌斯变换(Fast Mobius Transform&Mobius Inversion)

      九.另一个角度的反演

      十.总结

    一.反演定义

    形如下的式子即为反演:

    $$f(n)=sum_{i=0}^{n}a_{ni}g(n)$$

    $$g(n)=sum_{i=0}^{n}b_{ni}f(n)$$

    可以看出来,这实际上就是一个列向量与矩阵的乘积,反演就是解出这个方程。

    这个可以矩阵求逆或者高斯消元,但有些情况可以简单很多。

    一般来说,可以做文章的反演满足$a$和$b$都是下三角矩阵。

    下面逐个讨论这些反演公式。

    牢记:反演的本质是通过求逆矩阵解方程。

    二.二项式反演

    $$f(n)=sum_{i=0}^{n}inom{n}{i}g(i)$$

    $$g(n)=sum_{i=0}^{n}(-1)^{n-i}inom{n}{i}f(i)$$

    实际上就是容斥。

    应用:错位排列问题。

    证明:几乎反演证明都是一个套路,用VFK的话说就是“先找到一个if语句,然后说一句废话,代入化简即可”

    具体看讲义吧。

    三.莫比乌斯反演

    $$f(n)=sum_{d|n}g(d)$$

    $$g(n)=sum_{d|n}mu(frac{n}{d})f(d)$$

    实际上就是狄利克雷卷积。

    更本质地说,就是容斥。

    做题的一个套路:要求f,可以先找到一个比较好求的(限制放宽的)g,求出后反演解出f。

    我们看下莫比乌斯变换和反演到底怎么写:

    1 for (int i=1; i<=n; i++) f[i]=g[i];
    2 for (int i=1; i<=n; i++)
    3      for (int j=i+i; j<=n; j+=i) f[j]+=g[i]
    1 for (int i=1; i<=n; i++) g[i]=f[i];
    2 for (int i=1; i<=n; i++)
    3     for (int j=i+i; j<=n; j+=i) g[i]-=f[i];

    两个代码的区别实际上就是一个加减号。

    四.集合幂级数

    讨论另外几个反演之前,先要了解集合幂级数即相关内容。

    http://www.acyume.com/archives/2301

    与形式幂级数有很多相似之处,但OI中的集合幂级数的范围显然一般都比较小,因为无论怎样有枚举子集在内的算法复杂度都是指数级的,关键就在于如何减小底数。

    $$F(x)=sum_{Sin 2^x}f_Sx^S$$

    五.集合并卷积与集合对称差卷积

    就是满足两个集合的并集或对称差等于当前集合的所有集合幂级数的乘积。

    用数的语言描述就是

    $$c_n=sum_{i,j}[i or j=n]a_i*b_j$$

    $$c_n=sum_{i,j}[i xor j=n]a_i*b_j$$

    用集合的语言描述就是

    $$h_S=sum_{Psubseteq S,Qsubseteq S}[Pcup Q=S]f_P*g_Q$$$$h_S=sum_{Psubseteq S,Qsubseteq S}[Poplus Q=S]f_P*g_Q$$

    六.离散傅立叶变换(DFT)

    从多项式角度看是求值和插值,从卷积角度看就是变换和反演。

    $$c_n=sum_{p,q}[(p+q)mod p=n]a_p*b_q$$

    引入复数单位根求解。这已经到了形式幂级数和生成函数的范畴了。

    具体见http://www.cnblogs.com/HocRiser/p/8207295.html

    推导:

    $$egin{eqnarray} c_r & = & sum_{p, q} [(p + q) mod n = r] a_p b_q \ & = & sum_{p, q} frac{1}{n} sum_{k = 0}^{n - 1} epsilon^{-rk} epsilon^{pk} epsilon^{qk} a_p b_q \ & = & frac{1}{n} sum_{k = 0}^{n - 1} epsilon^{-rk} sum_{p, q} epsilon^{pk} a_p epsilon^{qk} b_q \ & = & frac{1}{n} sum_{k = 0}^{n - 1} epsilon^{-rk} sum_{p} epsilon^{pk} a_p sum_{q} epsilon^{qk} b_q \ end{eqnarray}$$

    反演形式:

    $$f_m=sum_{k=0}^{n-1}e^{mk}g_k$$

    $$g_m=frac{1}{n}sum_{k=0}^{n-1}e^{-mk}f_k$$

     

    七.快速沃尔什变换(FWT)

    没弄懂原理,但是可以直接用。

    递归版:https://www.cnblogs.com/y-clever/p/6979925.html

    蝴蝶操作版:https://blog.csdn.net/john123741/article/details/76576925

    每种运算都可以写出自己的反演式,这里不再逐个写出。

    复杂度和FFT一样是$O(nlog n)$的。

    例题:[BZOJ4589]Hard Nim

    就是n次异或FWT,中间全部直接用点值运算,最后IFWT回来即可。

     1 #include<cstdio>
     2 #include<cstring>
     3 #include<algorithm>
     4 #define rep(i,l,r) for (int i=l; i<=r; i++)
     5 typedef long long ll;
     6 using namespace std;
     7 
     8 const int N=(1<<18)+5,mod=998244353,rev=499122177;
     9 int n,m,len,s[N],t[N],a[N],b[N];
    10 
    11 void FWT(int a[],int n,int f,int op){
    12     for (int i=1; i<n; i<<=1)
    13         for (int p=i<<1,j=0; j<n; j+=p)
    14             for (int k=0; k<i; k++){
    15                 int x=a[j+k],y=a[i+j+k];
    16                 if (op==1){//or
    17                     if (f==1) a[i+j+k]=(x+y)%mod; else a[i+j+k]=(y-x+mod)%mod;
    18                 }
    19                 if (op==2){//and
    20                     if (f==1) a[j+k]=(x+y)%mod; else a[j+k]=(x-y+mod)%mod;
    21                 }
    22                 if (op==3){//xor
    23                     if (f==1) a[j+k]=(x+y)%mod,a[i+j+k]=(x-y+mod)%mod;
    24                         else a[j+k]=1ll*(x+y)*rev%mod,a[i+j+k]=((1ll*(x-y)*rev)%mod+mod)%mod;
    25                 }
    26     }
    27 }
    28 
    29 int main(){
    30     freopen("FWT.in","r",stdin);
    31     freopen("FWT.out","w",stdout);
    32     scanf("%d",&n); n=1<<n; len=n+n;
    33     for (int i=0; i<n; i++) scanf("%d",&s[i]);
    34     for (int i=0; i<n; i++) scanf("%d",&t[i]);
    35     rep(op,1,3){
    36         for (int i=0; i<len; i++) a[i]=s[i],b[i]=t[i];
    37         FWT(a,len,1,op); FWT(b,len,1,op);
    38         for (int i=0; i<len; i++) a[i]=1ll*a[i]*b[i]%mod;
    39         FWT(a,len,-1,op);
    40         for (int i=0; i<n; i++) printf("%d ",a[i]); puts("");
    41     }
    42     return 0;
    43 }
    Luogu4717

    八.快速莫比乌斯变换(FMT)

    https://www.cnblogs.com/Dance-Of-Faith/p/8818211.html

    对于上面的问题,只能处理集合并卷积。但是用它可以解决可重子集卷积问题。

    看完就会发现,所谓的数论中的莫比乌斯反演,实际上就是将每个数看成所有组成这个数的素数的可重集。

    类似于FWT和FFT,FMT一样是通过插值处理的。

    先看集合并卷积。

    对于集合幂级数$f$,求出它的莫比乌斯变换$hat{f}_S=sum_{Psubseteq S}f_P$。

    这样通过演算发现$hat{h}_S=hat{f}_S imes hat{g}_S$这里就是普通数值乘法了。

    这样,我们通过莫比乌斯反演可以由$hat{h}$求出$h$

    代码同样简单:

    1 for (int i=0; i<n; i++)
    2     for (int s=0; s<(1<<n); s++)
    3         if ((s>>i)&1) f[s]+=f[s^(1<<i)];
    1 for (int i=0; i<n; i++)
    2     for (int s=0; s<(1<<n); s++)
    3         if ((s>>i)&1) f[s]-=f[s^(1<<i)];

    写成反演形式就是:

    $$f(S)=sum_{Tsubseteq S}g(T)$$$$g(S)=sum_{Tsubseteq S}(-1)^{|S|-|T|}f(T)$$

    似曾相识?其实就是二项式反演的集合推广。

    接下来看子集卷积。

    $$c_r=sum_{psubseteq r}a_pb_{r-p}$$

    定义$mu(S)$,当S中含重复元素是为0,否则为$(-1)^{|S|}$。

    (其实就是数论里的$mu$)

    子集反演:

    $$f(S)=sum_{Tsubseteq S}g(T)$$$$g(S)=sum_{Tsubseteq S}mu(S-T)f(T)$$

    似曾相识?其实就是莫比乌斯反演的集合推广。

    下面仅考虑不可重集合的情况。

    和上面一样,先求出各个幂级数的FMT,然后卷起来。

    但是问题来了,这里不仅要求两个集合并集为当前集合,还要求两个集合不相交,这个就很难办了。

    注意到如果两个集合的大小满足$|a|+|b|=|c|$且元素满足$acup b=c$,那么这两个集合必然不相交。

    所以我们在反演的时候多加入一维集合大小,这样就可以向DP一样正确转移了。

    推导:

    $$egin{eqnarray} c_{r, i} & = & sum_{p, q subseteq r} [lvert p vert = i] [lvert q vert = lvert r vert - i] [p or q = r] a_p b_q \ & = & sum_{p, q subseteq r} [lvert p vert = i][lvert q vert = lvert r vert - i] sum_{v subseteq r} (-1)^{lvert r vert - lvert v vert} [p subseteq v] [q subseteq v] a_p b_q \ & = & sum_{v subseteq r} (-1)^{lvert r vert - lvert v vert} sum_{p, q subseteq v} [lvert p vert = i] [lvert q vert = lvert r vert - i] a_p b_q \ & = & sum_{v subseteq r} (-1)^{lvert r vert - lvert v vert} sum_{p subseteq v} [lvert p vert = i] a_p sum_{q subseteq v} [lvert q vert = lvert r vert - i] b_q end{eqnarray}$$

    1 for (int i=0; i<n; i++){
    2     for (int j=0; j<i; j++)
    3         for (int s=0; s<(1<<n); s++) h[i][s]+=f[j][s]*g[i-j][s];
    4     for (s=0; s<(1<<n); s++) if (cnt(s)!=i) h[i][s]=0;
    5 }

    FMT和IFMT:

    1 for (int i=0 i<n; i++)
    2     for (s=0; s<(1<<n); s++)
    3         if (s&(1<<i)) a[s]+=a[s^i];
    1 for (int i=0 i<n; i++)
    2     for (s=0; s<(1<<n); s++)
    3         if (s&(1<<i)) a[s]-=a[s^i];

     这样就解决了子集卷积的问题。

    时间复杂度:集合并卷积:$O(n2^n)$,子集卷积:$O(n^22^n)$。

    从数的角度看其实就是$O(nlog n)$和$O(nlog^2 n)$

    九.另一个角度的反演

    http://blog.miskcoo.com/2015/12/inversion-magic-binomial-inversion

    从容斥和矩阵的角度理解反演。

    实际上上面那么多反演,用到的其实都是一个式子:

    $$sum_{j=i}^{n}a_{nj}b_{ji}=delta_{ni}$$

    $$sum_{j=i}^{n}b_{nj}a_{ji}=delta_{ni}$$

    这也是上面所有式子能成立的根本原因。

    文中的代数证明在VFK的PPT中已经讲的很清楚了。

    十.总结

    反演是一个用处比较广泛的数学推导方法,OI中直接运用的不是特别多,但其衍生出的FFT,FWT和FMT都是很有力的工具。

    FFT开辟了一个形式幂级数的领域,而FWT和FMT则开辟了集合幂级数的领域。

    参考:http://vfleaking.blog.uoj.ac/blog/87

    2015年国家候选队论文集:《集合幂级数的性质与应用及其快速算法》

    本文大部分内容证明可以在这里找到,其余内容已在正文中加入链接。

    希望能对大家有帮助。

  • 相关阅读:
    记录
    Remote System Upgrade With Cyclone III Devices
    【Diary】Noip2020 游记
    【Diary】CSP-S 2020 游记
    【Diary】JZSC 2020 旅 游 记(迫真
    【题解】Luogu P2671 【求和】
    51nod 1153 选择子序列
    Luogu P4116 Qtree3
    Luogu P4114 Qtree1
    【Contest】Nowcoder 假日团队赛1 题解+赛后总结
  • 原文地址:https://www.cnblogs.com/HocRiser/p/8886806.html
Copyright © 2011-2022 走看看