[Luogu-CF662C]
FWT_xor
题目描述
有一个 \(n\) 行 \(m\) 列的表格,每个元素都是 $0/1 $,每次操作可以选择一行或一列,把 \(0/1\) 翻转,即把 \(0\) 换为 \(1\) ,把 \(1\) 换为 \(0\) 。请问经过若干次操作后,表格中最少有多少个 \(1\) 。
首先可以想到\(O(2^N*M)\)的暴力,即枚举每一行是否翻转,然后\(O(M)\)计算
设\(f_k\)表示选择了状态为\(k\)的那些行,\(a_i\)表示有多少列的二进制表示等于\(i\),\(b_j\)表示\(j\)中\(0\)个数和\(1\)个数的较小值,
我们有\(f_k=∑_{i⊗k=j}{a_i}{b_j}\),这相当于枚举对哪些行进行操作(\(k\)),然后对于二进制表示为\(i\)的列(有\(a_i\)列,做完行变换后这一列的值为\(i⊗k\)),贪心(\(b_j\))地选择是否对这一列做变换,用异或的性质把\(j,k\)互换,我们得到\(f_k=∑_{i⊗j=k}{a_i}{b_j}\),这是一个卷积的形式,可以用FWT快速求得答案
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define debug(...) fprintf(stderr,__VA_ARGS__)
#define Debug(x) cout<<#x<<"="<<x<<endl
using namespace std;
typedef long long LL;
const int INF=1e9+7;
inline LL read(){register LL x=0,f=1;register char c=getchar();while(c<48||c>57){if(c=='-')f=-1;c=getchar();}while(c>=48&&c<=57)x=(x<<3)+(x<<1)+(c&15),c=getchar();return f*x;
}const int N=20;
const int MAXN=1<<N;
const int MAXM=1e5+5;LL a[MAXN],b[MAXN];
char s[N][MAXM];
int n,m,len;inline void FWT(LL *A,int type){for(int i=1;i<len;i<<=1)for(int j=0;j<len;j+=(i<<1))for(int k=0;k<i;k++){LL x=A[j+k],y=A[j+i+k];A[j+k]=x+y,A[j+i+k]=x-y;if(type==-1) A[j+k]/=2,A[j+i+k]/=2;}
}int main(){n=read(),m=read();len=1<<n;for(int i=1;i<=n;i++) scanf("%s",s[i]+1);for(int i=1;i<=m;i++){int x=0;for(int j=1;j<=n;j++) x=(x<<1)+s[j][i]-'0';a[x]++;}for(int i=0;i<len;i++) b[i]=b[i>>1]+(i&1);for(int i=0;i<len;i++) b[i]=min(b[i],n-b[i]);FWT(a,1);FWT(b,1);for(int i=0;i<len;i++) a[i]*=b[i];FWT(a,-1);LL ans=a[0];for(int i=1;i<len;i++) ans=min(ans,a[i]);printf("%lld\n",ans);
}