ACM-ICPC 2018 徐州赛区网络预赛 B(博弈 + dp)

L

题意

起始有 m m 的价值,已知有 n 轮抉择,每轮抉择有三种,分别为 ai, bi, ci a i ,   b i ,   c i 其中任何一个值为 0 0 表示当前不可选。否则,可以选择将当前价值 +ai, bi, ×(1) 的一种。
若最后的总价值大于等于 k k ,则为 GoodEnding,若最后的总结值小于等于 l l ,则为 BadEnding,否则为 NormalEnding N o r m a l E n d i n g 。已知 A A 先手想要 GoodEnding B B 想要 BadEnding,问最后的结果是什么。

思路

我们无法确定当前轮抉择的原因是,不知道当前选择会对最后结果产生什么样的影响,但我们知道, A A 想让结果越大越好,B 想让结果越小越好,那么我们就从后往前 dp d p dp[i][j] d p [ i ] [ j ] 维护当在第 i i 轮,总价值为 j 时,所能得到的最优结果,因为这个最优是对 AB A B 相对而言的,所以根据当前是谁的轮次,维护最大值或者最小值即可。
有如下状态转移方程

dp[i][j]=max/min dp[i+1][j+ai]dp[i+1][jbi]dp[i+1][j] d p [ i + 1 ] [ j + a i ] d p [ i ] [ j ] = max / min   d p [ i + 1 ] [ j − b i ] d p [ i + 1 ] [ − j ]

又由于本题中 j j 的范围是 [100,100],所以我们需要让 j j 向后偏移100,之后注意遍历时的上下界即可

代码

int dp[maxn][205];
int a[maxn], b[maxn], c[maxn];
int bs = 100;

int main()
{
    int n, m, k, l;
    sdd(n, m);
    sdd(k, l);

    rep(i, 0, n)
        sddd(a[i], b[i], c[i]);

    rep(i, 0, 201)
        dp[n][i] = i;

    per(i, 0, n) {
        rep(j, 0, 201) {
            if(i & 1) { // B
                dp[i][j] = 200;
                if(a[i]) dp[i][j] = min(dp[i][j], dp[i+1][min(200, j+a[i])]);
                if(b[i]) dp[i][j] = min(dp[i][j], dp[i+1][max(0, j-b[i])]);
                if(c[i]) dp[i][j] = min(dp[i][j], dp[i+1][200-j]);
            }
            else {      // A
                dp[i][j] = 0;
                if(a[i]) dp[i][j] = max(dp[i][j], dp[i+1][min(200, j+a[i])]);
                if(b[i]) dp[i][j] = max(dp[i][j], dp[i+1][max(0, j-b[i])]);
                if(c[i]) dp[i][j] = max(dp[i][j], dp[i+1][200-j]);
            }
        }
    }

    if(dp[0][bs+m] >= bs+k) puts("Good Ending");
    else if(dp[0][bs+m] <= bs+l) puts("Bad Ending");
    else puts("Normal Ending");

    return 0;
}

小结

倒着 dp d p 可以是的当前轮次的选择有据可依,也就满足了“博弈时双方均会选择最优解”的这一点。妙啊

思路

另给出一个记忆化搜索过的代码,正确性证明类似于递归的出口是 i==n i == n 所以类似于倒着 dp d p ,两者一个是类似于 bfs b f s ,一个类似于 dfs d f s ,但是第一种只用了 2ms 2 m s ,第二种用了 25ms 25 m s 可能没差吧 = =

代码


int dp[maxn][205];
int a[maxn], b[maxn], c[maxn];
int bs = 100;
int n, m, l, k;

int dfs(int i, int j){

    if(i == n){
        if(j >= k+bs) return 2;
        if(j > l+bs) return 1;
        return 0;
    }
    if(dp[i][j] != -1) return dp[i][j];
    if(i&1) { // B
        int minn = 2;
        if(a[i]) minn = min(minn, dfs(i+1, min(j+a[i], 200)));
        if(b[i]) minn = min(minn, dfs(i+1, max(j-b[i], 0)));
        if(c[i]) minn = min(minn, dfs(i+1, 200-j));

        return dp[i][j] = minn;
    }
    else {
        int maxx = 0;
        if(a[i]) maxx = max(maxx, dfs(i+1, min(j+a[i], 200)));
        if(b[i]) maxx = max(maxx, dfs(i+1, max(j-b[i], 0)));
        if(c[i]) maxx = max(maxx, dfs(i+1, 200-j));

        return dp[i][j] = maxx;
    }
}


int main()
{
    mm(dp, -1);
    sdd(n, m);
    sdd(k, l);

    rep(i, 0, n)
        sddd(a[i], b[i], c[i]);

    rep(i, 0, 201)
        dp[n][i] = i;

    int judge = dfs(0, m+bs);

    if(judge == 2) puts("Good Ending");
    else if(judge == 0) puts("Bad Ending");
    else puts("Normal Ending");

    return 0;
}

Orz O r z

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值