Codeforces #662C Binary Table

听说这是一道$ Tourist$现场没出的题

Codeforces #662C

题意:

给定$n*m的 01$矩阵,可以任意反转一行/列($0$变$1$,$1$变$0$),求最少$ 1$的数量

$ n<=20 m<=100000$


$ Solution$

考虑暴力

枚举每一行反转/不反转

预处理$ g(s)$表示某状态为$ s$的列的最少$ 1$的数量

显然$ g(s)=min(popcount(s),n-popcount(s))$

枚举每行是否反转之后直接$ O(m)$计算即可

时间复杂度$ O(2^n m)$,无法通过这题

容易发现瓶颈在于暴力枚举行状态之后无法快速计算答案

我们令$ f(s)$表示列状态为$ s$的列的出现次数,$ F(s)$表示行反转状态为$ s$的时候的答案

转移有$ F(s)=sumlimits_{i=0}^{2^n-1}f(i)g(i xor s)$

由于$ i xor i   xor   s = s$

所以可以化简为$ F(s)=sumlimits_{i xor j =s}f(i)g(j)$

是一个$ FWT$卷积的形式

直接$ FWT$优化

时间复杂度:$ O(nm+2^n n)$

注意$ FWT$过程中可能要开$ long long$


$ my code:$

#include<ctime>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
#define rt register int
#define ll long long
using namespace std;
inline ll read(){
    ll x = 0; char zf = 1; char ch = getchar();
    while (ch != '-' && !isdigit(ch)) ch = getchar();
    if (ch == '-') zf = -1, ch = getchar();
    while (isdigit(ch)) x = x * 10 + ch - '0', ch = getchar(); return x * zf;
}
void write(ll y){if(y<0)putchar('-'),y=-y;if(y>9)write(y/10);putchar(y%10+48);}
void writeln(const ll y){write(y);putchar('
');}
int i,j,k,m,n,x,y,z,cnt,invn;
void fwt(int n,ll *a,int fla){
    for(rt i=1;i<n;i<<=1)
    for(rt j=0;j<n;j+=i<<1)
    for(rt k=0;k<i;k++){
        ll x=a[j+k],y=a[i+j+k];
        a[j+k]=x+y;a[i+j+k]=x-y;
    }
    if(fla==-1)for(rt i=0;i<n;i++)a[i]/=n;
}
char c[22][100010];
int s[100010];ll f[1048578],g[1048578];
#define cnt(x) __builtin_popcount(x)
int main(){
    n=read();m=read();
    for(rt i=1;i<=n;i++)scanf("%s",c[i]+1);
    for(rt i=1;i<=n;i++)
    for(rt j=1;j<=m;j++)s[j]=s[j]<<1|(c[i][j]=='1');
    for(rt i=1;i<=m;i++)g[s[i]]++;
    for(rt i=0;i<(1<<n);i++)f[i]=min(cnt(i),n-cnt(i));
    fwt(1<<n,f,1);fwt(1<<n,g,1);
    for(rt i=0;i<1<<n;i++)f[i]=f[i]*g[i];
    fwt(1<<n,f,-1);
    cout<<*min_element(f,f+(1<<n));
    return 0;
}
原文地址:https://www.cnblogs.com/DreamlessDreams/p/10038268.html