听说这是一道$ Tourist$现场没出的题
题意:
给定$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; }