题解 CF662C Binary Table

link

题意

给定一个 n×m01 矩阵,每次操作可以对一行或一列取反,问若干次操作后 1 的个数的最小值。

数据范围:1n20,1m2×105

题解

之前听 yls 讲过个不太类似的题,但也是根据本质不同的列的个数较小来做的,这个题就比较有思路。

容易发现取反次数不超过 1,操作顺序对结果不影响。

行数很小,用 ai 表示矩阵第 i 列,把这一列的信息压成一个值。

有个暴力是枚举每一行是否翻转,状态 s 的第 i 位表示第 i 行是否取反,每行取反后的 ai 就是初始的 ais,每次扫所有的列,考虑是否整列取反,即在 ai 中取 01 个数较小值,时间复杂度 O(m2n)

这个时间复杂度跟 m 有关,不太好优化。

发现本质不同的列数只有 O(2n) 个,用 fi 表示状态为 i 的列数有多少个,预处理出 gi 表示状态 i01 个数的较小值,令 ansi 表示每行取反状态为 i 的答案,易得

ansi=jfjgij

ansi=jk=ifjgk

那这个卷积形式不是跑个 FWT 就做完了?

时间复杂度为 O(n2n)

#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);
}
posted @   Terac  阅读(11)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
点击右上角即可分享
微信分享提示