天天看点

炫酷反演魔术 魔术揭秘开场揭秘收场

刚才的标题是唬人的…

请允许我先丢个链接,然后开始扯淡.

开场

vfk老师在这份课件中深入浅出地讲解了各种OI中常用的反演,本蒟蒻表示并没有完全看懂(尤其是对于矩阵的一部分知识不是非常熟悉),而且由于网上关于反演的资料除了莫比乌斯反演之外非常少,所以本蒟蒻在这里只是谈谈个人对反演的理解,如果出现了错误还请各路神犇不吝赐教.

还有就是课件中的推导已经写得非常漂亮了,我也没必要在这里再自己敲一遍(所以写这篇博客的目的何在?),有些地方还是直接”抛结论跑”好了.

揭秘

反演的思路是用未知量表示已知量,然后反过来推出未知量的表达式.下面我们默认表示已知量,表示未知量.

课件中介绍了二项式反演,莫比乌斯反演,子集反演等等,这些反演都与容斥原理有着密不可分的关系,就是说都可以用容斥来偏”意识流”地理解.但是不管怎么说,用反演来推导显得比较有说(zhuang)服(bi)力(fan).

推导反演的过程,就是”找到了一个if语句,说了一句废话,代进去搞搞居然就凑出来了个”.当然这其中需要很多技巧,可以通过看课件中的推导来体会.

二项式反演

推导的if语句是.

说的废话是.

莫比乌斯反演

一个方向:

另一个方向:

说实话我并不知道反方向的是怎么推的,但是结论非常好记.

if语句:.

废话:.

子集反演

一个方向:

另一个方向:

还是只会推正方向的…

if语句:.

废话:.

话说从这里开始变量名就有点奇怪了呢...

这个推导和二项式反演的推导非常相似.

实际运用时,这个东西可以用高维前缀和的写法来做,具体看下面的”卷积”部分.

离散傅里叶变换

这个东西实质上也是反演…但是我并不会用反演推…

给的if语句是.

然而我并不知道该说什么废话…

不过课件里那个”又一道经典题”是可以用这个if语句推的.

or卷积

给定,求:

关于这个名称应该没有正规的定义吧…似乎这个和下面的那个卷积都应该是”子集卷积”?还是看个人理解吧,我比较偏向于专门把下面的那个卷积称为”子集卷积”.

不管怎样,这个卷积是子集反演的一个经典应用.

令,类似地定义,可以推导出,求出后用子集反演求出.

具体实现时,直接略无脑地用高维前缀和正着倒着搞就可以实现子集反演了,复杂度是.

另外,卷积可以直接先把集合取补集转化成卷积来做.

Code

void or_conv(){
    rep(i,,n)rep(j,,<<n)if(j&<<i){
        A[j]+=A[j^<<i];
        B[j]+=B[j^<<i];
    }
    rep(i,,<<n)C[i]=A[i]*B[i];
    rep(i,,n)rep(j,,<<n)if(j&<<i)
        C[j]-=C[j^<<i];
}
           

子集卷积

给定,求:

[Math Processing Error]

这个…似乎课件上的推导有些问题(当然很有可能是我没看懂),这里良心地写一下我的推法.

如果直接用交集和并集的限制是推不出来的,我们必须再加一个维度.

令[Math Processing Error],

还有[Math Processing Error].

那么:

然后再用子集反演,可以推出:

最终答案就是.

这样复杂度就可以做到了.

但是直接暴力做,复杂度是,凭借极小的常数在时仍可以碾压标程….

Code

const int N=,M=<<N;

int n,bitcnt[M],A[M],B[M],C[M],sum_A[N+][M],sum_B[N+][M],sum_C[N+][M];

inline void init(){
    rep(i,,M)bitcnt[i]=bitcnt[i>>]+(i&);
}
void conv(){

    rep(i,,n+)rep(j,,<<n){
        if(i==bitcnt[j]){
            sum_A[i][j]=A[j];
            sum_B[i][j]=B[j];
        }
        else sum_A[i][j]=sum_B[i][j]=;
    }

    rep(i,,n)rep(j,,<<n)if(j&<<i)rep(k,,bitcnt[j]){
        mod_add(sum_A[k][j],sum_A[k][j^<<i]);
        mod_add(sum_B[k][j],sum_B[k][j^<<i]);
    }

    rep(i,,n+)rep(j,,<<n){
        sum_C[i][j]=;
        rep(k,,i+)sum_C[i][j]=(sum_C[i][j]+ll*sum_A[k][j]*sum_B[i-k][j])%mod;
    }

    rep(i,,n)rep(j,,<<n)if(j&<<i)rep(k,,n+)
        mod_minus(sum_C[k][j],sum_C[k][j^<<i]);

    rep(i,,<<n)C[i]=sum_C[bitcnt[i]][i];

}
           

收场

妈呀最怕这种时候要我总结点什么了!

大概我对反演的理解也就是这些了…

我觉得学习反演,不应该只记个结论,中间的推导过程也是很有价值的.

那就先这样吧.