九省联考2018D1T1 一双木棋

链接

可以观察到,整个对局过程中的任意时刻,棋盘总是被一条从左下到右上的轮廓线拆分成有子和无子的两部分。因此,我们可以考虑轮廓线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;
}


猜你喜欢

转载自blog.csdn.net/star_city_7/article/details/79949143