Description
给出一个 n×m 的 01 矩阵,每次操作可以反转某行或某列,问可以得到的 1 的最小值
Input
第一行两个整数
Output
输出经过若干次操作后矩阵里 1 的数量最小值
Sample Input
3 4
0110
1010
0111
Sample Output
2
Solution
注意到行数比较小,考虑枚举行的反转状态
ans[sta]=∑j=02n−1num[j]⋅dp[j
^
sta]=∑num[j]⋅dp[k],j
^
k=sta
用 FWT 求出序列 num 和 dp 的异或卷积即得到 ans 值, min{ans[i],0≤i≤2n−1} 即为答案
Code
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#include<set>
#include<ctime>
using namespace std;
typedef long long ll;
typedef pair<int,int>P;
const int maxn=(1<<20)+5;
int n,m,a[maxn],f[maxn];
ll num[maxn],dp[maxn];
char s[maxn];
void FWT(ll *a,int n)
{
for(int d=1;d<n;d<<=1)
for(int i=0;i<n;i+=(d<<1))
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 i=0;i<n;i+=(d<<1))
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;
}
}
int main()
{
scanf("%d%d",&n,&m);
int N=1<<n;
for(int i=0;i<n;i++)
{
scanf("%s",s+1);
for(int j=1;j<=m;j++)
if(s[j]=='1')a[j]^=(1<<i);
}
for(int i=1;i<=m;i++)num[a[i]]++;
for(int i=1;i<N;i++)f[i]=f[i/2]+(i&1);
for(int i=0;i<N;i++)dp[i]=min(f[i],n-f[i]);
FWT(dp,N),FWT(num,N);
for(int i=0;i<N;i++)dp[i]*=num[i];
UFWT(dp,N);
ll ans=dp[0];
for(int i=1;i<N;i++)ans=min(ans,dp[i]);
printf("%I64d\n",ans);
return 0;
}