题目
给定一个(n imes m)的01矩阵,你每次可以翻转一行或一列任意次。问操作若干次后矩阵中最少的1是多少?(nle 20),(m le 100000)
题解
(n)很小,所以可以将每一列状压为(a_i)。若干次操作后,行操作集合相当于一个掩码,列操作相当于是否对掩码取反然后异或到(a_i)上。设(f_i)代表(i)的二进制表示中1的个数,(g_i)代表列操作对应的掩码为(i)时的最小值。
[g_j=sumlimits_{i=1}^{m}{min(f_{a_iigoplus j},n-f_{a_iigoplus 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_{iigoplus j} cdot f_j}
]
直接使用fwt解决。
#include <bits/stdc++.h>
#define endl '
'
#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;
}