[九省联考2018]一双木棋chess
据说这题是可以暴力踩过去的。。
还是考虑正解吧,是一种叫 轮廓线dp 的只听过没写过的东西
不难发现,最后拿出来的棋子一定是左上角占的一块区域。发现 $ n + m leq 20 $ 我们可以状压一下这个区域右上到左下的边界。具体来说,我们存一个 $ 2^{n + m} $ 以内的数,1 表示向下 0 表示向左。
是否需要再开一维状态维护当前是哪个人在下?没必要,当确定轮廓线的时候已经下的步数是确定的,所以可以知道是谁在下。为了方便实现采用记搜。
#include "iostream"
#include "algorithm"
#include "cstring"
#include "cstdio"
using namespace std;
#define MAXN 12
int n , m;
int A[MAXN][MAXN] , B[MAXN][MAXN];
int dp[1 << 20];
int work( int sta , int w ) {
if( ~dp[sta] ) return dp[sta];
dp[sta] = w ? -0x3f3f3f3f : 0x3f3f3f3f;
int x = n , y = 0;
for( int i = 0 ; i < n + m - 1 ; ++ i ) {
if( sta & ( 1 << i ) ) -- x;
else ++ y;
if( ( ( sta >> i ) & 3 ) != 1 ) continue; // LefT,DowN
// LefT DowN -> DowN LefT
if( w ) dp[sta] = max( dp[sta] , work( sta ^ ( 3 << i ) , w ^ 1 ) + A[x + 1][y + 1] );
else dp[sta] = min( dp[sta] , work( sta ^ ( 3 << i ) , w ^ 1 ) - B[x + 1][y + 1] );
}
return dp[sta];
}
int main() {
cin >> n >> m;
for( int i = 1 ; i <= n ; ++ i ) for( int j = 1 ; j <= m ; ++ j ) scanf("%d",&A[i][j]);
for( int i = 1 ; i <= n ; ++ i ) for( int j = 1 ; j <= m ; ++ j ) scanf("%d",&B[i][j]);
memset( dp , -1 , sizeof dp );
dp[( 1 << n ) - 1 << m] = 0;
cout << work( ( 1 << n ) - 1 , 1 ) << endl;
}