1. 程式人生 > >[BZOJ5248] 2018九省聯考 D1T1 一雙木棋 | 博弈論 狀壓DP

[BZOJ5248] 2018九省聯考 D1T1 一雙木棋 | 博弈論 狀壓DP

while 奇數 getchar OS cst blog || ios char

題面

菲菲和牛牛在一塊\(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){
    if(x < 0) putchar('-'), x = -x;
    if(x >= 10) write(x / 10);
    putchar('0' + x % 10);
}

const int N = 15, INF = 0x3f3f3f3f;
int n, m, a[N][N], b[N][N], f[1 << 20];

void debug(int x){
    for(int i = 0; i < (n + m); i++)
    putchar('0' + (x >> i & 1));
}

int main(){

    read(n), read(m);
    for(int i = 1; i <= n; i++)
    for(int j = 1; j <= m; j++)
        read(a[i][j]);
    for(int i = 1; i <= n; i++)
    for(int j = 1; j <= m; j++)
        read(b[i][j]);
    for(int s = (1 << (n + m)) - 1, fi = 1; s >= 0; s--){
    int cnt1 = 0, area = 0;
    for(int i = 0; i < n + m; i++)
        if(s >> i & 1) cnt1++;
    if(cnt1 != n) continue;
    if(fi){
        fi = 0;
        continue;
    }
    for(int i = 0, x = n + 1, y = 1; i < n + m; i++){
        if(s >> i & 1) x--;
        else if(y++ <= m) area += x - 1;
    }
    f[s] = (area & 1) ? INF : -INF;
    for(int i = 0, x = n + 1, y = 1; i < n + m; i++){
        if(s >> i & 1) x--;
        else{
        if(i && (s >> (i - 1) & 1)){
            if(area & 1) f[s] = min(f[s], f[s ^ (3 << (i - 1))] - b[x][y]);
            else f[s] = max(f[s], f[s ^ (3 << (i - 1))] + a[x][y]);
        }
        y++;
        }
    }
    }
    write(f[(1 << n) - 1]), enter;

    return 0;
}

[BZOJ5248] 2018九省聯考 D1T1 一雙木棋 | 博弈論 狀壓DP