首页 > 代码库 > Codeforces 662C(快速沃尔什变换 FWT)

Codeforces 662C(快速沃尔什变换 FWT)

感觉快速沃尔什变换和快速傅里叶变换有很大的区别啊orz

不是很明白为什么位运算也可以叫做卷积(或许不应该叫卷积吧)

 

我是看 http://blog.csdn.net/liangzhaoyang1/article/details/52819835 里的快速沃尔什变换

这里说一下自己的理解吧,快速傅里叶变换是计算卷积的,就是∑f(x)*g(n-x)这种

快速沃尔什变换也是计算∑f(x)*g(y) ,但这里是计算所有的满足x^y = n(卷积是计算x+y=n)的和

当然,异或也可以换成&,|这些运算符。

正是因为这一点不同,所以fwt与fft有不同的构造方式,具体见引用的博客里的内容

 

下面说这道题的题解

题意很简单:就是给出一个01矩阵,每一次可以把一行或一列翻转,不限次数,计算最少有多少个1

首先,每一行只需被翻一次或者不翻,可以证明翻奇数次和翻一次等价,不翻和翻偶数次等价

所以就可以先暴力枚举某一行翻没翻,这样有一个m*2^n的复杂度。

那么怎么考虑用fwt呢

考虑一个翻的方案S,实际上第i列答案就是 min(f(S^a[i]), n - f(S^a[i])) (f可以计算1的个数)

所以也就是说,所有的S^a[i]为同一个值的答案是相同的(想一想x^y=n)

那么就处理出来dp[i] = min(f(i), n - f(i))和原矩阵的列中有多少个是i

对于一个方案S, 答案就是∑dp[S^i]*num[i] (注意x^y = S)

所以我们只需要算出dp和num的卷积,然后从中统计答案即可

 

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
typedef long long LL;
class FWT{
public:
    void fwt(LL *a, int n){
        for(int d = 1; d < n; d <<= 1){
            for(int m = d<<1, i = 0; i < n; i += m){
                for(int j = 0; j < d; j++){
                    LL x = a[i+j], y = a[i+j+d];
                    a[i+j] = x+y; a[i+j+d] = x-y;
                    //and a[i+j] = x+y;
                    //or a[i+j+d] = x+y;
                }
            }
        }
    }
    void ufwt(LL *a, int n){
        for(int d = 1; d < n; d <<= 1){
            for(int m = d<<1, i = 0; i < n; i += m){
                for(int j = 0; j < d; j++){
                    LL x = a[i+j], y = a[i+j+d];
                    a[i+j] = (x+y)/2; a[i+j+d] = (x-y)/2;
                    //and a[i+j] = x-y
                    //or a[i+j] = y-x
                }
            }
        }
    }
    void work(LL *a, LL *b, int n){
        fwt(a, n);
        fwt(b, n);
        for(int i = 0; i < n; i++) a[i] *= b[i];
        ufwt(a, n);
    }
}myfwt;
const int maxn = 100086;
char str[maxn];
LL dp[(1<<20)+5], num[(1<<20)+5], a[maxn];
int n, m;
int calc(int x){
    int ans = 0;
    for(; x; x >>= 1) ans += (x&1);
    return ans;
}
int main()
{
    cin>>n>>m;
    for(int i = 0; i < n; i++){
        cin>>str;
        for(int j = 0; j < m; j++)
            a[j] |= ( (str[j]-0) << i);
    }
    for(int i = 0; i < m; i++) num[a[i]]++;
    for(int i = 0; i < (1<<n); i++){
        int ans = calc(i);
        dp[i] = min(ans, n-ans);
    }
    myfwt.work(dp, num, 1<<n);
    LL ans = 1e18;
    for(int i = 0; i < (1<<n); i++) ans = min(ans, dp[i]);
    cout<<ans<<endl;
    return 0;
}

 

Codeforces 662C(快速沃尔什变换 FWT)