CF662C - Binary Table(fwt,dp)
题目
给定一个\(n\times m\)的01矩阵,你每次可以翻转一行或一列任意次。问操作若干次后矩阵中最少的1是多少?\(n\le 20\),\(m \le 100000\)
题解
\(n\)很小,所以可以将每一列状压为\(a_i\)。若干次操作后,行操作集合相当于一个掩码,列操作相当于是否对掩码取反然后异或到\(a_i\)上。设\(f_i\)代表\(i\)的二进制表示中1的个数,\(g_i\)代表列操作对应的掩码为\(i\)时的最小值。
\[g_j=\sum\limits_{i=1}^{m}{\min(f_{a_i\bigoplus j},n-f_{a_i\bigoplus j})}
\]
则答案为\(\min(g_i)\)。
时间复杂度为\(O(2^{2n})\),显然超时。为了快速计算\(g\),设\(h_i\)代表序列\(a\)中值为\(i\)的个数,\(cnt_i\)值为\(i\)时最少的1的个数,有
\[cnt_i=\min(f_{i},n-f_{i})
\]
\[g_i=\sum{cnt_{i\bigoplus j} \cdot f_j}
\]
直接使用fwt解决。
#include <bits/stdc++.h>
#define endl '\n'
#define IOS std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define mp make_pair
#define seteps(N) fixed << setprecision(N)
typedef long long ll;
using namespace std;
/*-----------------------------------------------------------------*/
ll gcd(ll a, ll b) {return b ? gcd(b, a % b) : a;}
#define INF 0x3f3f3f3f
const int N = 3e6 + 10;
const double eps = 1e-5;
ll f1[N], f2[N];
void fwt_xor(ll f[], int len) {
for(int l = 2; l <= len; l <<= 1) {
for(int i = 0, p = (l >> 1); i < len; i += l) {
for(int j = i; j < i + p; j++) {
ll a0 = f[j], a1 = f[j + p];
f[j] = a0 + a1;
f[j + p] = a0 - a1;
}
}
}
}
void ifwt_xor(ll f[], int len) {
for(int l = 2; l <= len; l <<= 1) {
for(int i = 0, p = (l >> 1); i < len; i += l) {
for(int j = i; j < i + p; j++) {
ll a0 = f[j], a1 = f[j + p];
f[j] = (a0 + a1) / 2;
f[j + p] = (a0 - a1) / 2;
}
}
}
}
int arr[N];
int count(int x) {
int res = 0;
while(x) {
if(x & 1) res++;
x >>= 1;
}
return res;
}
int main() {
IOS;
int n, m;
cin >> n >> m;
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++) {
char ch;
cin >> ch;
arr[j] += (ch - '0') << (i - 1);
}
}
for(int i = 1; i <= m; i++) {
f1[arr[i]]++;
}
for(int i = 0; i < (1 << n); i++) {
int num = count(i);
f2[i] = min(num, n - num);
}
fwt_xor(f1, 1 << n);
fwt_xor(f2, 1 << n);
for(int i = 0; i < (1 << n); i++) f1[i] = f1[i] * f2[i];
ifwt_xor(f1, 1 << n);
ll ans = f1[0];
for(int i = 1; i < (1 << n); i++) ans = min(ans, f1[i]);
cout << ans << endl;
}