[BZOJ5248] 2018九省联考 D1T1 一双木棋 | 博弈论 状压DP

题面

菲菲和牛牛在一块\(n\)\(m\)列的棋盘上下棋,菲菲执黑棋先手,牛牛执白棋后手。 棋局开始时,棋盘上没有任何棋子,两人轮流在格子上落子,直到填满棋盘时结束。

落子的规则是:一个格子可以落子当且仅当这个格子内没有棋子且这个格子的左侧及上方的所有格子内都有棋子。

棋盘的每个格子上,都写有两个非负整数,从上到下第i 行中从左到右第j 列的格 子上的两个整数记作\(A_{i, j}\)\(B_{i, j}\)。在游戏结束后,菲菲和牛牛会分别计算自己的得分:菲菲的得分是所有有黑棋的格子上的\(A_{i, j}\)之和,牛牛的得分是所有有白棋的格子上的\(B_{i, j}\)的和。

菲菲和牛牛都希望,自己的得分减去对方的得分得到的结果最大。现在他们想知道,在给定的棋盘上,如果双方都采用最优策略且知道对方会采用最优策略,那么,最终的结果如何。

题解

简单题。考场上的我是傻逼。

状压DP是容易想到的思路。若\(s\)是一个状态(先不管它是怎么压出来的),若\(s\)状态中有偶数个格子有棋子(现在轮到A下了),\(f[s]\)表示该状态之后的A得分与B得分之差的最大值(因为A想让这个得分差尽可能大);反之,若\(s\)中有奇数个格子有棋子,则轮到B下了,\(f[s]\)表示该状态之后的A得分与B得分之差的最小值

注意\(f[s]\)表示的是达到状态\(s\)之后的落子造成的得分差,而不是之前的!因为两个人的决策显然是根据【未来】做出的,而不是过去……(所以应该倒着枚举\(s\)

首先,一个格子能落棋子当且仅当:【棋盘左上角到该格子】这个矩形中,只有该格子是空的。

那么每时每刻,有棋子的格子和空格子的分布一定是形如这样的('#'代表有棋子,'-'代表无棋子)

######
###---
##----
##----
#-----

这启发了我们如何进行状压。
想象'#'与'-'的分界线,像这样:

      ___|
    _|
   |
  _|
_|

显然,分界线永远从左下走向右上,期间只【向左走】或【向上走】。

用0和1表示分界线的形状:1表示向上走一格,0表示向右走一格。

例如这个局面

####
###
###
#

它的分界线形状可以表示为01001101,是一个长为\(n + m\)的01串。

那么把01串作为二进制数来表示状态。由于串中一定有且只有\(n\)个1,所以总共有效的状态只有\(C_{n + m}^{n}\)个,但是直接\(2^{n + m}\)枚举就可以通过了。

总结一下:由大到小枚举状态\(s\),每次把其中的一个10改为01(这样分界线向外弯了一格,设这格为\((x, y)\)),设新状态为\(t\),则若状态\(s\)中'#'有偶数个,\(f[s] = max\{f[t] + A_{x, y}\}\),否则\(f[s] = min\{f[t] - B_{x, y}\}\)

代码:

#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <iostream>
#define enter putchar('\n')
#define space putchar(' ')
using namespace std;
typedef long long ll;
template <class T>
void read(T &x){
    char c;
    bool op = 0;
    while(c = getchar(), c < '0' || c > '9')
	if(c == '-') op = 1;
    x = c - '0';
    while(c = getchar(), c >= '0' && c <= '9')
	x = x * 10 + c - '0';
    if(op == 1) x = -x;
}
template <class T>
void write(T x){