题解 CF662C Binary Table

$\texttt{link}$

题意

给定一个 $n\times m$ 的 $01$ 矩阵,每次操作可以对一行或一列取反,问若干次操作后 $1$ 的个数的最小值。

数据范围:$1\le n\le20,1\le m\le2\times10^5$

题解

之前听 yls 讲过个不太类似的题,但也是根据本质不同的列的个数较小来做的,这个题就比较有思路。

容易发现取反次数不超过 $1$,操作顺序对结果不影响。

行数很小,用 $a_i$ 表示矩阵第 $i$ 列,把这一列的信息压成一个值。

有个暴力是枚举每一行是否翻转,状态 $s$ 的第 $i$ 位表示第 $i$ 行是否取反,每行取反后的 $a_i$ 就是初始的 $a_i \oplus s$,每次扫所有的列,考虑是否整列取反,即在 $a_i$ 中取 $0$ 和 $1$ 个数较小值,时间复杂度 $O(m2^n)$。

这个时间复杂度跟 $m$ 有关,不太好优化。

发现本质不同的列数只有 $O(2^n)$ 个,用 $f_i$ 表示状态为 $i$ 的列数有多少个,预处理出 $g_i$ 表示状态 $i$ 中 $0$ 和 $1$ 个数的较小值,令 $ans_i$ 表示每行取反状态为 $i$ 的答案,易得

$$ans_i=\sum\limits_{j}f_jg_{i\oplus j}$$

$$ans_i=\sum\limits_{j\oplus k=i}f_jg_k$$

那这个卷积形式不是跑个 FWT 就做完了?

时间复杂度为 $O(n2^n)$。

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1e5 + 10, M = (1 << 20) + 10;
int n, m, a[N];
ll f[M], g[M];
char s[N];
void FWT(ll *f, int T) {
    for(int mid = 1; mid < n; mid <<= 1)
        for(int i = 0; i < n; i += mid << 1)
            for(int j = 0; j < mid; j++) {
                ll x = f[i + j], y = f[i + j + mid];
                f[i + j] = (x + y) / T;
                f[i + j + mid] = (x - y) / T;   
            }
}
int main() {
    scanf("%d%d", &n, &m);
    for(int i = 0; i < n; i++) {
        scanf("%s", s + 1);
        for(int j = 1; j <= m; j++)
            if(s[j] - '0') a[j] |= 1 << i;
    }
    for(int i = 1; i <= m; i++)
        f[a[i]]++;
    for(int i = 0, w; i < 1 << n; i++) {
        w = __builtin_popcount(i);
        g[i] = min(w, n - w);
    }
    n = 1 << n;
    FWT(f, 1), FWT(g, 1);
    for(int i = 0; i < n; i++)
        f[i] *= g[i];
    FWT(f, 2);
    ll ans = 1e9;
    for(int i = 0; i < n; i++)
        ans = min(ans, f[i]);
    printf("%lld", ans);
}
posted @ 2022-10-25 21:51  Terac  阅读(21)  评论(0)    收藏  举报  来源