题意:给一个N*M的0-1矩阵,能够进行若干次操做,每次操做将一行或一列的0和1反转,求最后能获得的最少的1的个数.
分析:本题可用FWT求解.
由于其0-1反转的特殊性且\(N\leq20\),将每一列j视做一个N位二进制数\(A[j]\),则一共有M个N位数,则能够统计出每一个二进制数i的个数\(num[i]\).将全部的行反转操做组合也视做一个N位二进制数\(S\).
那么如何将本题与FWT结合? 首先根据异或运算的结合律:\(S\oplus A[j]=B\),则\(S = A[j] \oplus B\),\(B\)确定也是一个N位二进制数.处理出每一个二进制数对应最少的1的个数(由于咱们能够将某一列也反转)\(B[j]\),则对于每一种行操做的组合\(S\),最后获得最少的1的个数即 \(cnt(S) = \sum_{i\oplus j = S}(num[i]*B[j])\).用FWT计算出每一个操做组合\(S\)的最少1个数,最后扫一遍全部操做,求其最小值便可.c++
#include<bits/stdc++.h> using namespace std; const int MAXN = (1<<20)+5; typedef long long LL; void FWT(LL a[] ,int n){ for (int d = 1 ; d < n ; d <<= 1){ for (int m = d << 1 ,i = 0;i < n ; i+=m){ for (int j = 0 ; j < d ; j++){ LL x = a[i+j],y = a[i+j+d]; a[i+j] = (x+y),a[i+j+d] = (x-y); } } } } void UFWT(LL a[],int n){ for (int d = 1 ; d < n ; d<<=1){ for (int m = d <<1, i = 0; i < n; i+=m){ for (int j = 0 ; j < d ; j++){ LL x = a[i+j],y = a[i+j+d]; a[i+j] = (x+y)/2; a[i+j+d] = (x-y)/2; } } } } void solve(LL a[],LL b[],int n){ FWT(a,n); FWT(b,n); for (int i = 0 ; i<n ; i++) a[i]=a[i]*b[i]; UFWT(a,n); } LL A[MAXN],B[MAXN],num[MAXN]; char str[MAXN]; int main() { #ifndef ONLINE_JUDGE freopen("in.txt","r",stdin); freopen("out.txt","w",stdout); #endif int N,M; scanf("%d %d",&N, &M); for(int i=0;i<N;++i){ scanf("%s",str); for(int j=0;j<M;++j){ A[j] |= ((str[j]-'0')<<i); } } for(int i=0;i<M;++i) num[A[i]]++; for(int i=0;i<(1<<N);++i){ int cnt = __builtin_popcount(i); B[i] = min(cnt,N-cnt); } int all = 1<<N; solve(num,B,all); LL ans = N*M; for(int i=0;i<all;++i){ ans = min(ans,num[i]); } printf("%lld\n",ans); return 0; }