zoukankan      html  css  js  c++  java
  • [九省联考2018]一双木棋

    因为本题是两个人同时以自己最优的方式在进行博弈,因此是不能使用贪心来求解的,只能使用 (dp)

    首先不难发现如果一个位置 ((x, y)) 能落子那么 ((1, 1) sim (x, y)) 中的所有位置必须已经落子。

    那么每一个合法的状态都会形成一个阶梯状,从上到下往右扩展的长度不增。

    那么一个暴力的想法就是记录每一行的状态,但显然这样是不能通过本题的,需要另辟蹊径。

    但我们依然只能在状态上下手脚,在 AT1975 [ARC058C] 和風いろはちゃん 中出现过将十进制数压成 (2) 进制的方法,不妨来试一试。

    但你会发现这样的复杂度还是 (O(2 ^ {nm})) 的,甚至劣于原先的复杂度。

    但转念一想,本题和那道题不同的地方在于这道题填进去的数是依次递减的。

    这意味着所有增量之和是不超过 (m) 的!

    于是我们可以将这个高度从上到下差分,每次插入进去增量的长度即可,每一行之间使用 (0) 分隔开。

    具体来说,每新加入一行,我们先在字符串末尾加入一个 (0) 表示分隔,其次在加入若干个 (1) 数量为这一行的长度减下一行的长度。

    于是这样我们描述状态的复杂度就降至 (O(2 ^ {n + m})) 了。

    那么就只要考虑转移即可,我们简单记作 (dp_S) 表示状态为 (S) 时先手得分减后手得分的值。

    首先你可以发现每次将落下一个子后,如果加入的是第一行,相当于在最后的 (10) 之间插入了一个 (1) 状态值变大。

    如果放在最后一行,显然是在第一个 (1) 后添加一个 (0),状态值也会变大。

    如果放在中间的一行,相当于将 (S) 中的 (01) 交换成 (10),状态值也会变大。

    因此我们就发现了 (dp) 的拓扑序,也方便了下面的转移。

    但你会发现正着 (dp) 貌似是不行的,因为实质上一个人让自己最优后一定会让对手走入当前状态,而正着 (dp) 是不能继续接着上面的状态的。

    但是你会发现反过来 (dp) 刚好能使得后手进入的状态必定是先手决策以后的状态,因此对于这种对抗博弈,我们一般考虑反着 (dp)

    #include <bits/stdc++.h>
    using namespace std;
    #define rep(i, l, r) for (int i = l; i <= r; ++i)
    #define dep(i, l, r) for (int i = r; i >= l; --i)
    const int N = 20 + 5;
    const int M = (1 << 20) + 5;
    const int inf = 1e9;
    int n, m, maxl, final, dp[M], sta[M], cnt[M], a[N][N], b[N][N];
    int read() {
        char c; int x = 0, f = 1;
        c = getchar();
        while (c > '9' || c < '0') { if(c == '-') f = -1; c = getchar();}
        while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
        return x * f;
    }
    int Count(int x) { int ans = 0; for (; x; x >>= 1) if(!(x & 1)) ++ans; return ans;}
    void update(int v, int u, int x, int y) { 
        if(dp[v] == inf || dp[v] == -inf) return;
        if(sta[v]) dp[u] = max(dp[u], dp[v] + a[x][y]);
        else dp[u] = min(dp[u], dp[v] - b[x][y]);
    }
    int main() {
        n = read(), m = read(), maxl = (1 << (n + m)) - 1;
        rep(i, 1, n) rep(j, 1, m) a[i][j] = read();
        rep(i, 1, n) rep(j, 1, m) b[i][j] = read();
        rep(S, 0, maxl) {
            int tmp = 0, num = 0;
            dep(i, 0, n + m) {
                if(S & (1 << i)) ++tmp;
                else num += tmp;
            }
            dp[S] = (num & 1 ? inf : -inf), cnt[S] = Count(S), sta[S] = (num & 1);
        }
        rep(i, n, n + m - 1) final += (1 << i);
        dp[final] = 0;
        dep(S, 2, final - 1) {
            int P = 1, tmp = 0, res = 0; 
            rep(i, 1, n + m - 1) if(S & (1 << i)) ++P;
            if((S << 1) + 2 <= maxl && P <= m) update((S << 1) + 2, S, 1, P);
            for (P = n + m - 1; P && !(S & (1 << P)); --P); 
            if(S - (1 << P) + (1 << (P + 1)) <= maxl && cnt[S] < n) 
                update(S - (1 << P) + (1 << (P + 1)), S, cnt[S] + 1, 1);
            dep(i, 1, P) {
                if(!(S & (1 << i))) {
                    if(S & (1 << (i - 1))) update(S + (1 << i) - (1 << (i - 1)), S, cnt[S] - tmp, res + 1);
                    ++tmp;
                }
                else ++res;
            }
        }
        printf("%d", dp[2] + a[1][1]);
        return 0;
    }
    

    一般来说,压高维的字符串往往能写成压二进制串的形式,注意如果表达一个单调的序列可以记录差分数组,这样记录的总量级是序列最大值级别的。

    注意对抗博弈需要倒着 (dp)

    GO!
  • 相关阅读:
    Semaphore类
    我的java学习之路五:java的循环和条件语句
    我的java学习之路四:java的基础类型和变量
    第一节 线性表
    我的java学习之路三:java的类与对象
    我的Java学习之路二:Java基础语法
    算法分析一:基本定义
    我的java学习之路一:java的安装以及环境配置
    【2019 CCPC 江西省赛】Cotree 树重心
    【2018 icpc 南京站】G
  • 原文地址:https://www.cnblogs.com/Go7338395/p/13789452.html
Copyright © 2011-2022 走看看