Legend
...
(校内 OJ 题目不公开)
Link ( extrm{to HOJ})。
Editorial
(n) 特别小,考虑对每一列状压为 (n) 位二进制数。
对于每一行,考虑是否翻转, 则对于每一列可以贪心。但这样做复杂度太高。有没有办法可以一次性求出所有情况的答案?
答案是有,设 (H(i)) 为行翻转状态为 (i) 的答案,(F(i)) 为最初的列为 (i) 的列有多少个,(G(i)) 表示状态为 (i) 的列翻或不翻的最小代价,则:
[egin{aligned}
H(k)&=sum_{i=0}^{2^n-1}F(i)G(ioplus k)\
&=sum_{i=0}^{2^n-1}sum_{j=0}^{2^n-1}[ioplus k=j]F(i)G(j)\
&=sum_{i=0}^{2^n-1}sum_{j=0}^{2^n-1}[ioplus j=k]F(i)G(j)
end{aligned}
]
你惊人地发现这就是个异或 FWT!
时间复杂度 (O(nm+n2^n))。
Code
#include <bits/stdc++.h>
#define debug(...) ;//fprintf(stderr ,__VA_ARGS__)
#define __FILE(x)
freopen(#x".in" ,"r" ,stdin);
freopen(#x".out" ,"w" ,stdout)
#define LL long long
const int MX = 1 << 20;
const LL MOD = 1e9 + 7;
const LL inv = (MOD + 1) / 2;
int read(){
char k = getchar(); int x = 0;
while(k < '0' || k > '9') k = getchar();
while(k >= '0' && k <= '9') x = x * 10 + k - '0' ,k = getchar();
return x;
}
void chkmin(int &a ,int b){a = std::min(a ,b);}
int bitcnt(int a){
int x = 0;
while(a) a -= a & -a ,++x;
return x;
}
char str[20][MX];
int n ,m ,cnt[MX];
int G[MX] ,F[MX];
void FWT(int *A ,int limit ,int type){
for(int mid = 1 ; mid < limit ; mid <<= 1)
for(int j = 0 ; j < limit ; j += mid << 1)
for(int k = 0 ; k < mid ; ++k){
LL x = A[j + k] ,y = A[j + k + mid];
A[j + k] = (x + y) % MOD;
A[j + k + mid] = (x - y + MOD) % MOD;
if(type == -1){
A[j + k] = A[j + k] * inv % MOD;
A[j + k + mid] = A[j + k + mid] * inv % MOD;
}
}
}
int main(){
__FILE(game);
n = read() ,m = read();
for(int i = 0 ; i < n ; ++i) scanf("%s" ,str[i]);
for(int j = 0 ; j < m ; ++j){
int a = 0;
for(int i = 0 ; i < n ; ++i){
a |= (str[i][j] == '1') << i;
}
++F[a];
}
for(int i = 0 ; i < (1 << n) ; ++i){
int c = bitcnt(i);
G[i] = std::min(c ,n - c);
}
int limit = 1 << n;
FWT(F ,limit ,1) ,FWT(G ,limit ,1);
for(int i = 0 ; i < (1 << n) ; ++i) F[i] = 1LL * F[i] * G[i] % MOD;
FWT(F ,limit ,-1);
int ans = MX;
for(int i = 0 ; i < (1 << n) ; ++i)
ans = std::min(ans ,F[i]);
printf("%d
" ,ans);
return 0;
}