题目

点这里看题目。

分析

实在是一道巧妙的打表找规律分析题目!

不难想到每种翻转方案只需要最多执行一次。那么可以设 \(s_{i,j}\) 表示最终 \((i,j)\) 这个位置的值为 \((-1)^{s_{i,j}}a_{i,j}\)

接下来,这道题可以分析出两个重要结论,但是考虑到 They're much easier to be proven than found,所以我也只会证明

结论 1

对于任意的 \(1\le i<m,1\le j\le n\),一定满足:

\[s_{i,j}\oplus s_{m,j}\oplus s_{i+m,j}=0 \]

对应地,对于任意的 \(1\le i\le n,1\le j<m\),一定满足:

\[s_{i,j}\oplus s_{i,m}\oplus s_{i,j+m}=0 \]


如何发现这个结论?打表,但也不见得会变得简单

如何证明这个结论?“很简单”。以第一部分为例,注意到 \([i,i+m]\) 之中一共包含 \(m+1\) 个格子,并且 \(m=\frac{n+1}{2}\),所以不管怎样操作,只要有一次翻转到了这一列,那么 \((m,j)\) 就一定会被翻转到;此外,由于 \(1\le i<m\),所以无论怎样操作, \((i,j)\)\((i+m,j)\) 其中之一一定会被操作到。所以,每次翻转到这一行,这三个数有且只有两个数会被翻转,那么异或和自然为 0。

结论 2

上面的条件显然是一个有效的操作方案的必要条件,而这个结论则说,它也是充分的


如何发现这个结论?这倒没什么难度,直接从上面开始猜想即可。

如何证明这个结论?上面的结论证明,最终不同的 \(s\) 的个数 \(\le 2^{m^2}\)。而考虑到我们实际上只有 \(m^2\) 种不同的操作,并且每种操作最多进行一次,如果我们可以说明最终不同的 \(s\) 的个数 \(=2^{m^2}\),那么结论 1 必然是充分的。

这其实也不需要什么技巧。假如我们将每种操作看作一个 \(n^2\) 的向量,那么执行翻转就是对 \(s\) 异或上向量。考虑 \((1,1)\),只有一种操作,不妨称之为 \(v_1\),会影响到它,那么这一种操作和剩下的 \(m^2-1\) 种操作一定是异或意义下线性无关的。接着考虑 \((1,2)\),只有两种操作,一个是 \(v_1\)、另一个称之为 \(v_2\),会影响到它。考虑到线性组合 \(v_2\) 的时候 \(v_1\) 的系数一定为 0,所以 \(v_2\) 和其它操作仍然是线性无关的。一路推理我们可以说明 \(m^2\) 种操作都是线性无关的。那么最终 \(s\) 的个数恰好为 \(2^{m^2}\),因为每种操作执行与否都会真切地影响最终的 \(s\)


根据以上的推论,真正需要枚举的 \(s\) 其实只有 \(m^2\) 个。更进一步的,最重要的 \(s\) 其实坐落在中心十字上。如果我们枚举所有 \(1\le j\le m\)\(s_{m,j}\),那么剩下的 \(s\) 只会在行上相互影响,也就是行与行独立。我们只需要对于每一行,枚举中间位置的 \(s\),然后贪心地计算该行以及受该行影响的位置的结果即可。

时间复杂度为 \(O(2^m\times m^2)\)


另一种看法:用矩阵操作构造出“方形”、“分隔横行”、“分割纵列”、“四角散点”四种操作,这样所有的方形都可以利用横纵操作消除,最终只留下一个方形,可以利用横纵操作将它移到左上角;相似地,横行纵列可以利用散点来平移,从而彼此消除,最终在所在行、列上只留下一个位于边界的操作,接着就可以枚举情况了。

虽然我不会写,但是这个方法听起来很有意思。

小结:

  1. 一定要加强寻找结论的意识,如果觉得思考起来有困难、暴力不复杂、状态不过分多就可以尝试打表找规律
  2. 证明的思路都挺有意思的,尤其是借助方案数来推出充分的结论 2。
  3. 另一种看法里面的归约思想要掌握。
代码
#include <cstdio>

#define rep( i, a, b ) for( int i = (a) ; i <= (b) ; i ++ )
#define per( i, a, b ) for( int i = (a) ; i >= (b) ; i -- )

typedef long long LL;

const LL INF = 1e18;
const int MAXN = 40;

template<typename _T>
void read( _T &x )/*{{{*/
{
    x = 0; char s = getchar(); bool f = false;
    while( ! ( '0' <= s && s <= '9' ) ) { f = s == '-', s = getchar(); }
    while( '0' <= s && s <= '9' ) { x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar(); }
    if( f ) x = -x;
}/*}}}*/

template<typename _T>
void write( _T x )/*{{{*/
{
    if( x < 0 ) putchar( '-' ), x = -x;
    if( 9 < x ) write( x / 10 );
    putchar( x % 10 + '0' );
}/*}}}*/

template<typename _T>
_T MAX( const _T a, const _T b )/*{{{*/
{
    return a > b ? a : b;
}/*}}}*/

LL A[MAXN][MAXN];
int c[MAXN][MAXN];

int N, M;

#define Val( x, y ) ( c[x][y] ? - A[x][y] : A[x][y] )

int main()
{
    read( N ), M = ( N + 1 ) >> 1;
    rep( i, 0, N - 1 ) rep( j, 0, N - 1 ) read( A[i][j] );
    LL ans = - INF, res, tmp, col, cur; int m = M - 1;
    for( int S = 0 ; S < ( 1 << M ) ; S ++ )
    {
        c[m][m] = S >> ( M - 1 ) & 1;
        res = Val( m, m );
        for( int i = 0 ; i < m ; i ++ )
        {
            c[m][i] = S >> i & 1, res += Val( m, i );
            c[m][i + M] = c[m][m] ^ c[m][i], res += Val( m, i + M );	
        }
        for( int i = 0 ; i < m ; i ++ )
        {
            col = - INF;
            for( int &d = c[i][m] = 0 ; d < 2 ; d ++ )
            {
                c[i + M][m] = c[m][m] ^ c[i][m];
                tmp = Val( i, m ) + Val( i + M, m );
                for( int j = 0 ; j < m ; j ++ )
                {
                    cur = - INF;
                    for( int &e = c[i][j] = 0 ; e < 2 ; e ++ )
                    {
                        c[i][j + M] = c[i][j] ^ c[i][m];
                        c[i + M][j] = c[i][j] ^ c[m][j];
                        c[i + M][j + M] = c[i][j + M] ^ c[m][j + M];
                        cur = MAX( cur, Val( i, j ) + Val( i, j + M ) + 
                                  Val( i + M, j ) + Val( i + M, j + M ) );
                    }
                    tmp += cur;
                }
                col = MAX( col, tmp );
            }
            res += col;
        }
        ans = MAX( ans, res );
    }
    write( ans ), putchar( '\n' );
    return 0;
}