題意:給一個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; }