题目描述
给出一个n*m的01矩阵,每次可以反转一行或一列上的01,问最少能留下多少个1
n ≤ 20 , m ≤ 1 0 5 n\leq20,m\leq10^5 n≤20,m≤105
Sol
一看
n
≤
20
n\leq20
n≤20,于是想状压DP
但是这样就是
2
20
2^{20}
220次方了,根本没办法继续dp什么的了
一个显然的结论是如果行已经确定了,那么每一列的最有决策是确定的,每一列对答案的贡献就是0和1个数中的较小值
但是列数很多,不过我们可以先处理出每一列状态为
i
i
i 的列的个数
C
[
i
]
C[i]
C[i]
然后记当状态为
i
i
i时的对答案的贡献
F
[
i
]
F[i]
F[i]
那么一开始如果不做行变换时的答案就是
∑
F
[
i
]
∗
C
[
i
]
\sum F[i]*C[i]
∑F[i]∗C[i]
现在考虑做一次行变换会造成什么影响,也就是说对于本来的一个
F
[
i
]
F[i]
F[i],与之相乘的不是
C
[
i
]
C[i]
C[i]了,而是要把它的一个二进制位取反后的一个
C
[
j
]
C[j]
C[j]
至此,一个构造卷积的思路就很清晰了,假设我们使用行变换的行的集合为
i
i
i,那么此时的答案
A
A
A就是:
A
[
i
]
=
∑
j
F
[
j
]
∗
C
[
j
⊕
i
]
A[i]=\sum_{j}F[j]*C[j\oplus i]
A[i]=j∑F[j]∗C[j⊕i]
而我们发现
j
⊕
(
j
⊕
i
)
=
i
j\oplus(j\oplus i)=i
j⊕(j⊕i)=i,所以:
A
[
i
]
=
∑
j
⊕
k
=
i
F
[
j
]
∗
C
[
k
]
A[i]=\sum_{j\oplus k=i} F[j]*C[k]
A[i]=j⊕k=i∑F[j]∗C[k]
这不就直接是个卷积了吗,FWT完事
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=21,M=1e5+1;
const int MAXN=1<<20;
char Map[N][M];
ll f[MAXN],bitc[MAXN],num[MAXN];
int n,m;
inline void FWT(ll *A,int n,int f)
{
for(int i=1;i<n;i<<=1)
for(int p=i<<1,j=0;j<n;j+=p)
for(int k=0;k<i;++k){
ll X=A[j|k],Y=A[j|k|i];
A[j|k]=X+Y;A[j|k|i]=X-Y;
if(!f) A[j|k]/=2,A[j|k|i]/=2;
}
return;
}
int main()
{
scanf("%d %d",&n,&m);
for(int i=1;i<=n;++i) scanf("%s",Map[i]+1);
for(int i=1;i<=m;++i) {
int ati=0;
for(int j=1;j<=n;++j){
if(Map[j][i]=='1') ati|=1<<j-1;
}
++num[ati];
}
int MAX=1<<n;
for(int i=1;i<MAX;++i) {
bitc[i]=bitc[i>>1]+(i&1);
f[i]=min(bitc[i],1ll*n-bitc[i]);
}
FWT(f,MAX,1);FWT(num,MAX,1);
for(int i=0;i<MAX;++i) f[i]=f[i]*num[i];
FWT(f,MAX,0);
ll ans=1e9;
for(int i=0;i<MAX;++i) ans=min(ans,f[i]);
printf("%lld\n",ans);
return 0;
}