「CF321D」Ciel and Flipboard
題目
點這裡看題目。
分析
實在是一道巧妙的打表找規律分析題目!
不難想到每種翻轉方案只需要最多執行一次。那麼可以設 \(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\)
如何發現這個結論?打表,但也不見得會變得簡單
如何證明這個結論?“很簡單”。以第一部分為例,注意到 \([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\)
根據以上的推論,真正需要列舉的 \(s\) 其實只有 \(m^2\) 個。更進一步的,最重要的 \(s\) 其實坐落在中心十字上。如果我們列舉所有 \(1\le j\le m\) 的 \(s_{m,j}\),那麼剩下的 \(s\) 只會在行上相互影響,也就是行與行獨立。我們只需要對於每一行,列舉中間位置的 \(s\),然後貪心地計算該行以及受該行影響的位置的結果即可。
時間複雜度為 \(O(2^m\times m^2)\)。
另一種看法:用矩陣操作構造出“方形”、“分隔橫行”、“分割縱列”、“四角散點”四種操作,這樣所有的方形都可以利用橫縱操作消除,最終只留下一個方形,可以利用橫縱操作將它移到左上角;相似地,橫行縱列可以利用散點來平移,從而彼此消除,最終在所在行、列上只留下一個位於邊界的操作,接著就可以列舉情況了。
雖然我不會寫,但是這個方法聽起來很有意思。
小結:
- 一定要加強尋找結論的意識,如果覺得思考起來有困難、暴力不復雜、狀態不過分多就可以嘗試打表找規律。
- 證明的思路都挺有意思的,尤其是藉助方案數來推出充分的結論 2。
- 另一種看法裡面的歸約思想要掌握。
程式碼
#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;
}