正题
题目链接:https://www.luogu.com.cn/problem/CF662C
题目大意
n∗mn*mn∗m的网格上有0/10/10/1,可以任意翻转行和列,求剩下最少的111。
解题思路
知道是FWTFWTFWT之后就好做很多了。
首先因为nnn很小,所以可以考虑枚举翻转的行数,我们现在需要对于一个行的翻转状态快速知道最小值。
如果一行状态sss翻转行状态www之后就相当于val(sxorw)val(s\ xor\ w)val(s xor w)反过来也就是若c=sxorwc=s\ xor\ wc=s xor w那么sxorc=ws\ xor\ c=ws xor c=w。
此时就可以上FWTFWTFWT了,设valival_ivali表示列状态iii的最小值,numinum_inumi表示列状态iii的数量。那么ans(s)=∑ixorj=svali×numjans(s)=\sum_{i\ xor\ j=s}val_i\times num_jans(s)=i xor j=s∑vali×numj
上FWTFWTFWT即可,时间复杂度O(2nn+nm)O(2^nn+nm)O(2nn+nm)
codecodecode
#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);
}