[Easy-Medium] AT_abc396_g [ABC396G] Flip Row or Col
2025/3/20约 680 字大约 3 分钟
简要题意
给定一个 的 矩阵 ,你可以进行任意次操作,每次操作选定一行或一列,将每一个单元格的数字取反,你需要最小化所有数的和,输出这个最小值。
。
思路
首先注意到 是可以接受的,于是可以考虑枚举每一列是否操作,然后快速判断一行是否操作(操作显然可交换)。
根据这个思路,可以发现我们需要求的就是下面这个式子:
其中 表示 的第 行的 bitmask。
于是只需要快速计算上述式子即可。注意到我们要求和的东西是一个关于 的函数,不妨记 ,则将式子改写为:
由于 结构和异或在本题中似乎不太相容,于是我们只需要对于每一个 求出和式的值,然后取最小值即可。我们直接考察和式:
尝试改为枚举 :
其中 即 在 中的出现次数。
将和式进一步改写得:
这是 XOR 卷积的形式,直接用 FWT 解决即可。时间复杂度 。
代码
#include <bits/stdc++.h>
using namespace std;
constexpr int mod = 998244353, inv2 = (mod + 1) >> 1;
int Add(int x, int y){ return (x + y) >= mod ? (x + y - mod) : (x + y); }
int Sub(int x, int y){ return (x - y) < 0 ? (x - y + mod) : (x - y); }
int Mul(int x, int y){ return 1ll * x * y % mod; }
const int N = 2e5 + 5, M_ = (1 << 18) + 5;
int n, m, a[N], f[M_], c[M_];
void fwt(int *a, int n, int tag){
for(int x=2;x<=n;x<<=1){
int k = x >> 1;
for(int i=0;i<n;i+=x){
for(int j=0;j<k;j++){
a[i + j] = Add(a[i + j], a[i + j + k]);
a[i + j + k] = Sub(a[i + j], Add(a[i + j + k], a[i + j + k]));
a[i + j] = Mul(tag, a[i + j]);
a[i + j + k] = Mul(tag, a[i + j + k]);
}
}
}
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> n >> m;
for(int i=1;i<=n;i++){
string x; cin >> x;
for(int j=1;j<=m;j++) a[i] |= ((x[j - 1] - '0') << (j - 1));
c[a[i]]++;
}
for(int i=0;i<(1<<m);i++) f[i] = min(__builtin_popcount(i), m - __builtin_popcount(i));
fwt(c, 1 << m, 1), fwt(f, 1 << m, 1);
for(int i=0;i<(1<<m);i++) f[i] = Mul(f[i], c[i]);
fwt(f, 1 << m, inv2);
cout << *min_element(f, f + (1 << m)) << '\n';
return 0;
}
// Written by xiezheyuan