[CF 321D]Ciel and Flipboard解题报告
题意
有一个n*n矩阵aij。n为奇数,m=(n+1)/2。我们每次可以选中一个m*m的子矩阵,将其中所有元素乘以-1.求最后矩阵中所有元素的最大和。n<=33.
分析
这道题是个结论题。
对于第i行的三个值:j、m、m+j,每个子矩阵要么覆盖其中的0个要么覆盖其中2个。
例如:n=5,m=3,那么对于某行的第1、3、4这三个元素,子矩阵要么覆盖其中0个要么覆盖其中2个。对于2、3、5也是如此。
显然对于列也有类似结论。
如果我们把正反视作01(一开始均为0,乘以-1就异或1),设(i,j)的翻转状态为setup[i][j],那么setup[i][j]^setup[i][m]^setup[i][m+j]=0.当然这里要求k<m。类似地,setup[i][j]^setup[m][j]^setup[i+m][j]=0.于是我们只要知道其中两个,就能求出来第三个。
而且,只要setup矩阵满足这一条件,就一定能用一系列操作变换出来这个setup矩阵。
证法其实挺巧的:将每个a[i][j]都视作一维,那么每个m*m的子矩阵都可以视作n*n维线性空间中的向量。所有这些向量都是线性无关的(直观上看,不可能用若干个子矩阵操作将某一个别的子矩阵操作抵消掉)。而一共有m*m种不同的操作,因此最终一定有2^(m*m)个不同的setup矩阵。另一方面,只要确定了左上角的m*m个数,那整个setup矩阵亦随之确定,总数也是2^(m*m)种。所以每个满足条件的setup矩阵一定能变换出来。
因此我们可以O(2^m)枚举setup矩阵第m列的前m个数。确定它们之后,第m列的后n-m个数亦随之确定。然后可以发现,对于j<m,每个setup[m][j]都是独立的。那我们可以分别求出setup[m][j]取0、1时对答案的最大贡献:若setup[m][j]确定,则setup[m][j+k]确定,且对于每个i<m,当setup[i][j]确定时,setup[i][j+m]、setup[i+m][j]、setup[i+m][j+m]均唯一确定,枚举之即可。
总复杂度O(m*2^m)。
代码
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> using namespace std; const int INF=0x7fffffff/2; const int SIZEN=50; int N,M; int A[SIZEN][SIZEN]; int setup[SIZEN][SIZEN]; int sgn(int a){//0是未翻,1是翻了 return !a?1:-1; } int single_val(int x,int y){ return sgn(setup[x][y])*A[x][y]; } int calc_unit(int x,int y,int d){//(x,y)的四元组,要求中线均已放在setup中 int ans=0; setup[x][y]=d; ans+=single_val(x,y); setup[x][y+M]=setup[x][y]^setup[x][M]; ans+=single_val(x,y+M); setup[x+M][y]=setup[x][y]^setup[M][y]; ans+=single_val(x+M,y); setup[x+M][y+M]=setup[x+M][y]^setup[x+M][M]; ans+=single_val(x+M,y+M); return ans; } int calc_unit(int x,int y){//(x,y)的四元组的最大值 return max(calc_unit(x,y,0),calc_unit(x,y,1)); } int calc_left(int k,int d){//钦点第M行k列的值为d setup[M][k]=d; setup[M][k+M]=d^setup[M][M]; int ans=0; ans+=single_val(M,k)+single_val(M,k+M); for(int i=1;i<M;i++) ans+=calc_unit(i,k); return ans; } int calc_left(int k){//计算第M行k列的最大方案 return max(calc_left(k,0),calc_left(k,1)); } int calc_all(void){//第M列的1~M行已经决定了,求最大值 for(int i=1;i<M;i++) setup[i+M][M]=setup[i][M]^setup[M][M]; int ans=0; for(int i=1;i<=N;i++) ans+=single_val(i,M); for(int i=1;i<M;i++) ans+=calc_left(i); return ans; } void work(void){ int ans=-INF; for(int s=0;s<(1<<M);s++){ for(int i=1;i<=M;i++){ setup[i][M]=((s>>(i-1))&1); } ans=max(ans,calc_all()); } printf("%d\n",ans); } void read(void){ scanf("%d",&N); M=(N+1)/2; for(int i=1;i<=N;i++){ for(int j=1;j<=N;j++){ scanf("%d",&A[i][j]); } } } int main(){ //freopen("t1.in","r",stdin); read(); work(); return 0; }