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);
}
posted @ 2021-01-05 20:51  QuantAsk  阅读(102)  评论(0编辑  收藏  举报