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

Problem

  • 给定一个n(≤20)*m(≤100 000)的01矩阵,每次操作可以将一行或一列取反。
  • 求最终1的最少个数。

Solution

  • 前置技能:快速沃尔什变换(FWT)
  • 观察到n较小,考虑 O(2n) O ( 2 n ) 枚举每一行选或不选。
  • 不妨设f(x)表示行的操作状态为x时(我们可用一个二进制数表示状态),经过各种列操作后所得到的最少的1的个数。
  • 可以 O(m) O ( m ) 再扫一遍所有列。但显然T飞了。

  • 定义 Cj C j 表示有多少列的状态为j; Ek E k 表示对于某一列而言,若它经过各种行操作状态变成了k,则它再经历各种列操作后最少能得到的1的个数。
  • 显然, Cj C j 我们对于每一列统计一下即可;而 Ek E k 也很好求,设状态k中有cnt个1,则 Ek=min(cnt,ncnt) E k = m i n ( c n t , n − c n t ) (不进行/进行列操作)。
  • 而且我们也可以得到一个较为显然的式子: f(x)=xj=kCjEk f ( x ) = ∑ x ⊕ j = k C j ∗ E k 。这个式子的思路就是对于所有状态为j的列,我们都通过状态为x的行操作令其变成了 xj=k x ⊕ j = k ,然后再看看变成了k以后的答案。
  • 可以暴力枚举j,暴力转移。但是这样的复杂度是 O(22n) O ( 2 2 n ) 的。

  • 注意到xor的特殊性:对于任何 xy=z x ⊕ y = z ,有 yz=x y ⊕ z = x
  • 因此,上式可化为: f(x)=jk=xCjEk f ( x ) = ∑ j ⊕ k = x C j ∗ E k
  • 观察到这是一个卷积的形式,我们用FWT优化它。

  • 时间复杂度: O(nm+2nn) O ( n m + 2 n n )

Code

#include <bits/stdc++.h>
#define go(i,a,b) for(i=a;i<b;i++)
using namespace std;
typedef long long ll;

const int N=21,M=1e5+1,S=1<<21;
int i,j,n,m,s,tmp;
char str[M];
bool a[N][M];
ll c[S],e[S],f[S],ans;

void FWT(ll *tf)  
{  
    for(int d=1;d<s;d<<=1)  
        for(int m=d<<1,i=0;i<s;i+=m)  
            for(int j=0;j<d;j++)  
            {  
                ll x=tf[i+j],y=tf[i+j+d];  
                tf[i+j]=x+y; tf[i+j+d]=x-y;  
            }  
}  

void UFWT()  
{  
    for(int d=1;d<s;d<<=1)  
        for(int m=d<<1,i=0;i<s;i+=m)  
            for(int j=0;j<d;j++)  
            {  
                ll x=f[i+j],y=f[i+j+d];  
                f[i+j]=x+y>>1; f[i+j+d]=x-y>>1;  
            }  
}

int main()
{
    scanf("%d%d",&n,&m);
    go(i,0,n) 
    {
        scanf("%s",str);
        go(j,0,m) a[i][j]=str[j]-48;
    }

    go(i,0,m)
    {
        s=0;
        go(j,0,n) s+=a[j][i]*(1<<j);
        c[s]++;
    }

    s=1<<n;
    go(i,0,s) 
    {
        for(tmp=i; tmp; tmp>>=1) e[i]+=tmp&1;
        e[i]=min(e[i],n-e[i]);
    }

    FWT(c); FWT(e);
    go(i,0,s) f[i]=c[i]*e[i];
    UFWT();

    ans=n*m;
    go(i,0,s) ans=min(ans,f[i]);
    printf("%lld",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值