题解 CF662C Binary Table
题意
给定一个 $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);
}

浙公网安备 33010602011771号