CF-662C Binary Table
题解:这题难度思维还是有点大,如果要靠自己想出来的话。
题意就是n*m的矩形,每次可以反转一行或者一列,无数次。问最少的1的个数。
n<=20 m<= 1e5
这就有意思了,n<=20,这一看就是想让我们状压嘛。。
直接把20行每一列压成一个二进制数f[i]。
同时之前学位运算的时候我们知道,异或0表示不变,异或1表示取反,那么我们行操作就相当于每一个a[i]都异或行那一位的1,其他位保持0就可以了。
对于列,一列翻转前后只有两种结果,所以我们可以设f[i]表示列a[i]翻转前后最少有多少个1!这里C++自带函数可以统计二进制1的个数,当然自己手写也可以。
__builtin_popcount(x)
那么我们可以得到一个初步的做法:枚举K a n s ( k ) = ∑ i = 0 m − 1 f [ a [ i ] ⨁ k ] ans(k) = \sum_{i=0}^{m-1}f[a[i]\bigoplus k] ans(k)=∑i=0m−1f[a[i]⨁k]
但是这样枚举K,每次计算 o ( m ) o(m) o(m)太慢了,所以就想办法加速。
考虑转换一下式子,对于所有 a [ i ] ⨁ k a[i]\bigoplus k a[i]⨁k相同的数来说,他们对答案的贡献是一样的。同时对于枚举的K,K是相同的。所以只要提前预处理a[i]的个数就可以把式子改写为: ∑ f [ a [ i ] ⨁ k ] ∗ n u m [ a [ i ] ] \sum {f[a[i]\bigoplus k] * num[a[i]]} ∑f[a[i]⨁k]∗num[a[i]] 这时候就需要一点思维的技巧了,因为 a [ i ] ⨁ k ⨁ a [ i ] = k a[i]\bigoplus k \bigoplus a[i] = k a[i]⨁k⨁a[i]=k 所以我们可以大力改写! ∑ i ⨁ j = k f [ i ] ∗ n u m [ j ] \sum_{i\bigoplus j=k}{f[i]*num[j]} ∑i⨁j=kf[i]∗num[j] 其中i,j的含义就是刚才括号内的含义。 有了这个式子就可以跑一遍FWT求出所有的 a n s ( k ) ans(k) ans(k) 了!
现在我们考虑一下fwt的Len,因为其中涉及到的是a[i],那么上限应该就是a[i]的上限,a[i]是n位状压而成,最大值是(1<<n)-1 所以令Len=(1<<n)即可。
预处理f[i]和num[i]都很简单
for(int i = 0;i < m;i ++)//预处理Num
{
tong[a[i]] ++;
}
for(int i = 0;i < len;i ++) f[i] = min(__builtin_popcount(i),n-__builtin_popcount(i));//预处理f
完整代码
#include <bits/stdc++.h>
#define mem(a,b) memset((a),(b),sizeof(a))
using namespace std;
typedef long long ll;
const ll INF = 10000000000000000;
const int N = (1<<20) + 10;
const ll mod = 1e9+7;
void fwt(ll a[],int n){
for(int d=1;d<n;d<<=1){
for(int m=d<<1,i=0;i<n;i+=m){
for(int j=0;j<d;j++){
ll x=a[i+j],y=a[i+j+d];
a[i+j] = (x + y), a[i+j+d] = x - y;
}
}
}
//xor : a[i+j] = x + y, a[i+j+d] = (x - y + mod) % mod;
//and : a[i+j] = x + y;
//or : a[i+j+d] = x + y;
}
ll inv2;
void ifwt(ll a[],int n){
for(int d=1;d<n;d<<=1){
for(int m=d<<1,i=0;i<n;i+=m){
for(int j=0;j<d;j++){
ll x=a[i+j],y=a[i+j+d];
a[i+j] = (x + y)/2, a[i+j+d] = (x - y)/2;
}
}
}
//inv2 = 2^(-1)
//xor : a[i+j] = (x + y) / 2, a[i+j+d] = (x - y) / 2;
//and : a[i+j] = x - y;
//or : a[i+j+d] = y - x;
}
ll a[N],f[N],tong[N];
char s[N];
int main(){
inv2 = 1ll*(mod+1ll)/2ll;//2的逆元
int n,m;
scanf("%d%d",&n,&m);
getchar();
for(int i = 1;i <= n;i ++)
{
scanf("%s",s);
for(int j = 0;j < m;j ++)
{
a[j] = ((a[j] << 1) | (s[j] - '0'));
}
}
int len = 1 << n;
for(int i = 0;i < m;i ++)//预处理Num
{
tong[a[i]] ++;
}
for(int i = 0;i < len;i ++) f[i] = min(__builtin_popcount(i),n-__builtin_popcount(i));//预处理f
fwt(tong,len);
fwt(f,len);
for(int i=0;i<len;i++)
{
f[i] = f[i] * tong[i];
}
ifwt(f,len);
ll minn = INF;
for(int i = 0;i < len;i ++)
{
minn = min(minn,f[i]);
}
printf("%lld\n",minn);
}
/*
3 7
4 13
*/