可以观察到,整个对局过程中的任意时刻,棋盘总是被一条从左下到右上的轮廓线拆分成有子和无子的两部分。因此,我们可以考虑轮廓线dp,从左下角开始,向上的边为1,向右的边为0。设f[sta]表示当状态为sta时,双方都选择最优方案还能获得的收益,则初始状态(也就是最终对局结果)为f[11...1100...00],最终状态(也就是开始对局的状态)为f[00...0011...11]。状态倒着读,因为低位在右边。
然后考虑转移。当一颗棋子落下时,棋盘落子处的轮廓线将会从“上——右”变为“右——上”,对应的状态某处也会从f[...01...]变为f[...10...]。单纯的dp转移不好想,那么就选择记忆化搜索。
时间复杂度C(n+m, n),空间复杂度2^n。
#include <iostream>
#include <cstring>
const int inf = 0x3f3f3f3f;
using namespace std;
int n, m;
int a[13][13];
int b[13][13];
int f[1 << 20];
int dfs(int sta, bool now) //sta为状态,now为当前轮到谁走
{
if (f[sta] != inf) return f[sta];
f[sta] = now ? -inf : inf;
int x = n, y = 0;
for (int i = 0; i < n + m - 1; i++) { //遍历轮廓线
if (sta >> i & 1) x--;
else y++;
if ((sta >> i & 3) != 1) continue;
int nxt = sta ^ (3 << i); //把01变10
if (now) f[sta] = max(f[sta], dfs(nxt, now ^ 1) + a[x][y]); //a希望a-b更大
else f[sta] = min(f[sta], dfs(nxt, now ^ 1) - b[x][y]); //b希望b-a更大,相当于a-b更小
}
return f[sta];
}
int main(void)
{
cin >> n >> m;
for (int i = 0; i < n; i++)
for (int j = 0; j < m; j++)
cin >> a[i][j];
for (int i = 0; i < n; i++)
for (int j = 0; j < m; j++)
cin >> b[i][j];
memset(f, 0x3f, sizeof f);
f[(1 << n) - 1 << m] = 0;
cout << dfs((1 << n) - 1, 1);
return 0;
}