#Description
给定一个n*m的01矩阵,可以任意对整行、整列进行异或操作。可以操作任意次。问最后矩阵中最少有多少个1
n<=20,m<=10^5
#Analysis
发现n很小,小到我们可以枚举出行的全部操作状态,且可以将一列看成一个整数。那么只需要快速求出在次状态下列的贡献和。
设
c
s
c_s
cs为状态为
s
s
s时的答案,那么如果令
a
x
a_x
ax为初始矩阵中列的值为
x
x
x的个数,
b
x
b_x
bx为值为
x
x
x的列最少可能有多少个1,有
c
k
=
∑
i
x
o
r
k
=
j
a
i
b
j
c_k=\sum_{i\ xor\ k=j}a_ib_j
ck=∑i xor k=jaibj
c
k
=
∑
i
x
o
r
j
=
k
a
i
b
j
c_k=\sum_{i\ xor\ j=k}a_ib_j
ck=∑i xor j=kaibj
于是可以用FWT优化运算。
#include<cstdio>
#include<cstring>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;++i)
#define fd(i,b,a) for(int i=b;i>=a;--i)
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)<(y)?(x):(y))
#define mset(a,x) memset(a,x,sizeof(a))
using namespace std;
typedef long long ll;
char ch;
int read(){int n=0,p=1;for(ch=getchar();ch<'0' || ch>'9';ch=getchar())if(ch=='-') p=-1;for(;'0'<=ch && ch<='9';ch=getchar()) n=n*10+ch-'0';return n*p;}
const int N=22,S=(1<<22)+2,M=1e5+5;
int n,m,len;
ll c[S],a[S],b[S];
bool map[N][M];
void dwt(ll *a,int sig=1)
{
for(int i=2;i<=len;i<<=1)
for(int j=0;j<len;j+=i)
for(ll *l=a+j,*r=a+j+(i>>1),k=0;k<(i>>1);++l,++r,++k)
{
ll u=*l+*r,v=*l-*r;
*l=u,*r=v;
if(sig==-1) *l>>=1,*r>>=1;
}
}
int main()
{
scanf("%d %d\n",&n,&m);
fo(i,1,n)
{
fo(j,1,m) map[i][j]=getchar()=='1';
scanf("\n");
}
fo(j,1,m)
{
int t=0;
fo(i,1,n) t=t*2+map[i][j];
++a[t];
}
fo(i,0,(1<<n)-1)
{
int cnt=0;
fo(j,0,n-1) if(i&(1<<j)) ++cnt;
b[i]=min(cnt,n-cnt);
}
len=1<<n;
dwt(a);
dwt(b);
fo(i,0,len-1) c[i]=a[i]*b[i];
dwt(c,-1);
int ans=n*m;
fo(i,0,(1<<n)-1) ans=min(ans,c[i]);
printf("%d",ans);
return 0;
}