题目描述
有一个(n)行(m)列的表格,每个元素都是(0/1),每次操作可以选择一行或一列,把(0/1)翻转,即把(0)换为(1),把(1)换为(0)。请问经过若干次操作后,表格中最少有多少个(1)。
题解
但凡跟状态转移扯上一点关系的题目就没我什么事了……
首先,我们可以枚举每一行是否反转,然后每一次就可以(O(m))的计算出答案,然而这样的复杂度是(O(2^nm))的,会挂
我们设(a[S])表示状态为(S)的列有多少个,(b[S])表示状态(S)中(0)和(1)的个数中较小的那一个,设当前的行状态为(now),可以有如下转移$$ans=sum_{i=0}^{2^n}a[i] imes b[i⊕now]$$
因为有(i⊕i⊕now=now),所以转移可以写成如下形式$$ans[now]=sum_{i⊕j=now}a[j]*b[j]$$
用(FWT)优化即可,复杂度为(O(2^nlog(2^n))=O(n*2^n))
//minamoto
#include<bits/stdc++.h>
#define R register
#define ll long long
#define fp(i,a,b) for(R int i=a,I=b+1;i<I;++i)
#define fd(i,a,b) for(R int i=a,I=b-1;i>I;--i)
#define go(u) for(int i=head[u],v=e[i].v;i;i=e[i].nx,v=e[i].v)
template<class T>inline bool cmin(T&a,const T&b){return a>b?a=b,1:0;}
using namespace std;
const int N=(1<<20)+5;
char s[N];int sz[N],g[N],n,m,lim;ll A[N],B[N],ans=1e9;
void FWT(ll *A,int ty){
for(R int mid=1;mid<lim;mid<<=1)
for(R int j=0;j<lim;j+=(mid<<1))
for(R int k=0;k<mid;++k){
ll x=A[j+k],y=A[j+k+mid];
A[j+k]=x+y,A[j+k+mid]=x-y;
if(ty==-1)A[j+k]/=2,A[j+k+mid]/=2;
}
}
signed main(){
// freopen("testdata.in","r",stdin);
scanf("%d%d",&n,&m),lim=1<<n;
fp(i,1,n){
scanf("%s",s+1);
fp(j,1,m)g[j]|=(s[j]-'0')<<(i-1);
}fp(i,1,m)++A[g[i]];
fp(i,1,lim-1)sz[i]=sz[i>>1]+(i&1);
fp(i,0,lim-1)B[i]=min(sz[i],n-sz[i]);
FWT(A,1),FWT(B,1);fp(i,0,lim-1)A[i]=A[i]*B[i];
FWT(A,-1);fp(i,0,lim-1)cmin(ans,A[i]);
printf("%d
",ans);return 0;
}