CF662C Binary Table
题意:
有一个 n 行 m 列的表格,每个元素都是 0/1 ,每次操作可以选择一行或一列,把 0/1 翻转,即把 0 换为 1 ,把 1 换为 0 。请问经过若干次操作后,表格中最少有多少个 1
n<=20,m<=1e5
题解:
参考
洛谷题解第一篇,讲的太详细了
因为行很小(n<=20),列很大(m<=1e5),因为我们可以考虑枚举反转了哪些行。设X表示翻转了哪些行(X是一个整数,其二进制表示翻转的状态)
对于任意一列,设第i列的状态为SiS_iSi,也是其二进制表示该列的状态
现在开始考虑每一列都变成什么样子:
设第i列翻转后变成了状态YiY_iYi,显然有Yi=Si⨁XY_i=S_i \bigoplus XYi=Si⨁X
对于一个相同的X(即一个行翻转方案),最优答案1的个数是唯一的,而X最多只有220=10485762^{20}=1048576220=1048576,也就是我们可以直接枚举X
因为每一列都是相互独立的,为了让表格中1最少,对于每一列翻转到最少个1位置,设状态为i时经过翻转这个二进制最少有FiF_iFi个1
比如n=3,(3)10=(011)2(3)_{10}=(011)_2(3)10=(011)2,翻转后为(100)2(100)_2(100)2
所以F3=1F_3=1F3=1
答案就是∑i=1mF{X⨁Si}\sum_{i=1}^mF_{\{X \bigoplus S_i\}}∑i=1mF{X⨁Si}
我们枚举所有X,再枚举每一列状态的S,总复杂度是O(2nm)O(2^nm)O(2nm),还是不行
继续对式子优化:我们枚举Yi=Si⨁XY_i=Si \bigoplus XYi=Si⨁X
式子为:∑i=1m∑Y=02n[Y==Si⨁X]FY\sum_{i=1}^m\sum_{Y=0}^{2^n}[Y==S_i \bigoplus X]F_Yi=1∑mY=0∑2n[Y==Si⨁X]FY
我们试着换掉第一个枚举m的∑\sum∑,设所有列中有QiQ_iQi列的状态为i,相当于用桶来存
这样就可以得到:
∑S=02n∑Y=02n[Y==S⨁X]FY×QS\sum_{S=0}^{2^n}\sum_{Y=0}^{2^n}[Y==S \bigoplus X]F_Y×Q_SS=0∑2nY=0∑2n[Y==S⨁X]FY×QS
这样枚举每个S,不用再枚举[1,m]
不过还是没啥卵用,因为此时复杂度是O(22n)O(2^{2n})O(22n),好像还更差了,但与m无关了
此时这个式子就有些猫腻了
∑S=02n∑Y=02n[Y==S⨁X]FY×QS\sum_{S=0}^{2^n}\sum_{Y=0}^{2^n}[Y== S \bigoplus X]F_Y×Q_SS=0∑2nY=0∑2n[Y==S⨁X]FY×QS
⇔∑S=02n∑Y=02n[Y⨁S=X]FY×QS⇔\sum_{S=0}^{2^n}\sum_{Y=0}^{2^n}[Y \bigoplus S= X]F_Y×Q_S⇔S=0∑2nY=0∑2n[Y⨁S=X]FY×QS
⇔∑Y⨁SFY×QS⇔\sum_{Y \bigoplus S}F_Y×Q_S⇔Y⨁S∑FY×QS
设ANS[X]=∑Y⨁S=XFY×QSANS[X]=\sum_{Y \bigoplus S=X}F_Y×Q_SANS[X]=∑Y⨁S=XFY×QS
这这。。不就是FWT吗?答案不就是F和Q的xor卷积,这样复杂度就变成了O(2n∗log(2n))=O(2n∗n)O(2^n*log(2^n))=O(2^n*n)O(2n∗log(2n))=O(2n∗n),完美解决
代码:
#include <bits/stdc++.h>
#include <unordered_map>
#define debug(a, b) printf("%s = %d\n", a, b);
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> PII;
clock_t startTime, endTime;
//Fe~Jozky
const ll INF_ll= 1e18;
const int INF_int= 0x3f3f3f3f;
void read(){};
template <typename _Tp, typename... _Tps> void read(_Tp& x, _Tps&... Ar)
{x= 0;char c= getchar();bool flag= 0;while (c < '0' || c > '9')flag|= (c == '-'), c= getchar();while (c >= '0' && c <= '9')x= (x << 3) + (x << 1) + (c ^ 48), c= getchar();if (flag)x= -x;read(Ar...);
}
template <typename T> inline void write(T x)
{if (x < 0) {x= ~(x - 1);putchar('-');}if (x > 9)write(x / 10);putchar(x % 10 + '0');
}
void rd_test()
{
#ifdef ONLINE_JUDGE
#elsestartTime = clock ();freopen("data.in", "r", stdin);
#endif
}
void Time_test()
{
#ifdef ONLINE_JUDGE
#elseendTime= clock();printf("\nRun Time:%lfs\n", (double)(endTime - startTime) / CLOCKS_PER_SEC);
#endif
}
int n,m;
const int maxn=1e5+9;
const int maxn2=(1<<20)+9;
ll Q[maxn2],F[maxn2],S[maxn2],ANS[maxn];
ll a[30][maxn];
void FWT(ll x[],int t1,int t2,int len)
{const ll inv2= 2;for(int i=1;i<len;i<<=1)for(int j=0;j<len;j+=(i<<1))for(int k=0;k<i;k++){ll p=x[j+k],q=x[i+j+k];if(t1==0) x[i+j+k]=(q+t2*p); //orelse if(t1==1) x[j+k]=(p+t2*q); //andelse if(t1==2) //xor{x[j+k]=(p+q)/(t2<0?inv2:1);x[i+j+k]=(p-q)/(t2<0?inv2:1);} }
}
int main()
{rd_test();cin>>n>>m;for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){scanf("%1d",&a[i][j]);}}int len=1<<n;//S为每一列的状态,预处理出S for(int i=1;i<=m;i++){//列 for(int j=1;j<=n;j++){//行 S[i]|=(1<<(j-1))*a[j][i];}}//状态为i时经过翻转这个二进制最少有F[i]个1 for(int i=0;i<len;i++){F[i]=F[i>>1]+(i&1);}for(int i=0;i<len;i++){F[i]=min(F[i],n-F[i]);//正着与翻转着取min }//所有列中有Q[i]列的状态为i,就相当于是个桶 for(int i=1;i<=m;i++)Q[S[i]]++;FWT(F,2,1,len);FWT(Q,2,1,len);for(int i=0;i<len;i++)ANS[i]=F[i]*Q[i];FWT(ANS,2,-1,len);//在len种翻转方案种取最小值 ll minn=9999999999; for(int X=0;X<len;X++)minn=min(ANS[X],minn);cout<<minn<<endl;//开始卷Q和F//Time_test();
}