天天看點

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);
}