Binary Table
题意
CF662C
有个 \(n(\leq20)\) 行 \(m(\leq10^5)\) 列的 01 矩阵,每次操作可以翻转一行或一列的颜色,最小化 1 的个数,并输出。
暴力
注意到 \(n\) 很小,可以枚举所有行翻转的选择。
那么对于其中一种情况,只要能算出 \(m\) 列中每一列的最小 1 的个数,相加即是答案。
时间复杂度 \(O(m \times 2^n)\)。
肯定 T 飞。
优化
对于一列,它的状态是 \(s\)(1到n行的状态),如果当前选择的行翻转状态为 \(x\), 那么现在的状态就是 \(x \oplus s\)。
对于同样的状态,可以记其个数为 \(cnt_s\), 并且可以预处理出状态 \(s\), 最小的 1/0 的个数 \(val_s\)。
答案就是:
\[\sum_{x = 0}^{2^n} cnt_s \times val_{s \oplus x}
\]
变一下形式:
\[\sum_{x = s \oplus y} cnt_s \times val_y
\]
这就是异或卷积的形式。
用 fwt 优化,时间复杂度 \(O(n \times 2^n)\)。
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
using ll = long long;
const int MAXN = 1200010;
const int INF = 1e18;
const int mod = 1000000007;
//#define ll long long
template <typename T>
void Read(T &x) {
x = 0; T f = 1; char a = getchar();
for(; a < '0' || '9' < a; a = getchar()) if (a == '-') f = -f;
for(; '0' <= a && a <= '9'; a = getchar()) x = (x * 10) + (a ^ 48);
x *= f;
}
int n, m;
int a[21][MAXN], cnt[MAXN], val[MAXN];
void fwt(int *f, int n, int op) {
for (int p = 2; p <= n; p <<= 1) {
int len = p >> 1;
for (int k = 0; k < n; k += p)
for (int i = k; i < k + len; i ++) {
int left = f[i], right = f[i + len];
f[i] = (left + right);
f[i + len] = (left - right);
if (op == -1) f[i] >>= 1, f[i + len] >>= 1;
}
}
}
char s[MAXN];
signed main() {
cin >> n >> m ;
for (int i = 1; i <= n; i ++) {
cin >> s + 1;
for (int j = 1; j <= m; j ++)
a[i][j] = s[j] - '0';
}
for (int j = 1; j <= m; j ++) {
int sum = 0;
for (int i = 1; i <= n; i ++)
sum |= (1 << i - 1) * a[i][j];
cnt[sum] ++;
}
for (int i = 1; i < (1 << n); i ++) {
int ct = 0;
for (int j = i; j; j ^= j & -j) ct ++;
val[i] = min(ct, n - ct);
}
fwt(cnt, (1 << n), 1);
fwt(val, (1 << n), 1);
for (int i = 0; i < (1 << n); i ++)
cnt[i] = cnt[i] * val[i];
fwt(cnt, (1 << n), -1);
int ans = INF;
for (int i = 0; i < (1 << n); i ++)
ans = min(ans, cnt[i]);
cout << ans;
return 0;
}