CF662C-Binary Table【FWT】
正题
题目链接:https://www.luogu.com.cn/problem/CF662C
题目大意
\(n*m\)的网格上有\(0/1\),可以任意翻转行和列,求剩下最少的\(1\)。
解题思路
知道是\(FWT\)之后就好做很多了。
首先因为\(n\)很小,所以可以考虑枚举翻转的行数,我们现在需要对于一个行的翻转状态快速知道最小值。
如果一行状态\(s\)翻转行状态\(w\)之后就相当于\(val(s\ xor\ w)\)反过来也就是若\(c=s\ xor\ w\)那么\(s\ xor\ c=w\)。
此时就可以上\(FWT\)了,设\(val_i\)表示列状态\(i\)的最小值,\(num_i\)表示列状态\(i\)的数量。那么$$ans(s)=\sum_{i\ xor\ j=s}val_i\times num_j$$
上\(FWT\)即可,时间复杂度\(O(2^nn+nm)\)
\(code\)
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const ll N=(1<<21);
ll n,m,val[N],num[N],ans;
char s[20][110000];
void FWT(ll *f,ll op){
for(ll p=2;p<=n;p<<=1)
for(ll k=0,len=p>>1;k<n;k+=p)
for(ll i=k;i<k+len;i++){
ll x=f[i],y=f[i+len];
f[i]=(x+y)/op;
f[i+len]=(x-y)/op;
}
return;
}
void solve(ll *a,ll *b){
FWT(a,1);FWT(b,1);
for(ll i=0;i<n;i++)
a[i]=a[i]*b[i];
FWT(a,2);return;
}
int main()
{
scanf("%lld%lld",&n,&m);
for(ll i=0;i<n;i++)
scanf("%s",s[i]);
ll MS=(1<<n);
for(ll i=1;i<MS;i++)
val[i]=val[i-(i&-i)]+1;
for(ll i=0;i<MS;i++)
val[i]=min(val[i],n-val[i]);
for(ll i=0;i<m;i++){
ll w=0;
for(ll j=0;j<n;j++)
w+=(s[j][i]=='1')<<j;
num[w]++;
}
n=MS;solve(val,num);ans=1e9;
for(ll i=0;i<n;i++)
ans=min(ans,val[i]);
printf("%lld\n",ans);
}