zoukankan      html  css  js  c++  java
  • 区间dp与滚动数组——[USACO10DEC]宝箱Treasure Chest

    题目:P3004 [USACO10DEC]宝箱Treasure Chest

    ( extit{蒟蒻在DP之路上求索着})

    这道题要求每次只能从最左边或者最右边取走硬币,这是一个经典的区间DP模型.

    思路

    在刚刚看到这个题目的时候,我首先像个傻子一样想到了"哪边大拿哪边不就好了".但是只需要三秒钟就可以找到一个反例:

    [1,2,114514,3 ]

    别忘了,对手也会取最优策略.

    也就是说,我们的行动不只可以为了眼前拿到最大的,也可以是为了逼迫对手能走出对自己有好处的一步.这种长远打算的题目,怎么能离得开DP呢.一个比较初步的想法:我们可以设(f[i][j])表示两人取走从(i)(j)的一段,先手能得到的最大收益区间DP中常用的思想就是考虑最后一步,或者说最短的区间是怎样做的,然后递推到更长的区间.那我们就想象"时光倒流"的过程.接下来我们考虑怎么转移.

    当我们添加新的一步的时候,无非就是两种情况:在左边加或者在右边加.我们最终要取走这个区间所有的硬币,因此我们让对手取走的最少,剩下的自然就都是自己的了.我们怎么表示对手取得了多少呢?事实上并不用专门去表示.当我们设(f[i][j])表示拿走(i)(j)这一段,先手能得到的最大价值的时候,就已经不分敌我了.这个区间谁先手只由区间长度取决,我们不必关心.因为最后求出整个区间的先手最大值一定是自己的(题目要求自己先手嘛).

    那么我们现在处于区间([l,r]),该怎么转移呢?就像我说的,两个人取走的和必然是(sum[l,r]).因此从当前先手的角度考虑,(sum[l,r]-min(f[l+1][r],f[l][r-1])),也就是区间和减去对手的最小值就是我的最大值.区间长度只减少1的话,之前的先手肯定是对手,他会采取最优策略,因此我们就由之前已经推出的(f)数组就可以了.也就是说:

    [f[l][r]=sum[l][r]-min(f[l+1][r],f[l][r-1]) ]

    (sum)数组可以用前缀和快速求出因此我们写出了这道题的方程

    然后就爆空间了

    这是非常悲哀的.因为空间限制只有64M.这就要求我们对空间的优化.这就要提到我们的滚动数组了.因为我们只需要用到之前一个长度的区间,而且相同长度的区间我们只取一个最优解,因此之前的都可以舍去了.那么这是怎么做到的呢?我们之前枚举左右端点相当于枚举长度.那么我们直接一维枚举长度,然后一维枚举左端点,不就可以直接转移到这个区间了吗?具体来说,我们设(f[i])表示左端点为(i)的区间中先手能取得的最大值.我们的最终目标是(f[1]).那么转移方程就是:

    [f[i]=sum[i+L-1]-sum[i-1]-min(f[i],f[i+1]),1le Lle n ]

    注意到,我们的(min)中是(f[i]和f[i+1]),这就是滚动数组高效利用空间的体现.在转移之前,(f[i])就是以(i)为左端点,长度为(L-1)的最大值,(f[i+1])就是以(i+1)为左端点,长度为(L-1)的最大值.这两个值恰好代表了之前的(f[l][r-1])(f[l+1][r]).

    代码:

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    using namespace std;
    
    int n, a[5005], f[5005];
    
    inline int min(int x, int y) {
        return x < y ? x : y;
    }
    
    int main() {
        cin >> n;
        for (int i = 1; i <= n; i++) {
            cin >> a[i];
            f[i] = a[i];//不难理解的初始化
            a[i] += a[i - 1];//省略前缀和数组
        }
        for (int l = 2; l <= n; l++)//一定要由短区间转移到长区间,因此外层枚举长度
            for (int i = 1; i + l - 1<= n; i++)//枚举左端点
                f[i] = a[i + l - 1] - a[i - 1] - min(f[i], f[i + 1]);
        cout << f[1] << endl;
        return 0;
    }
    
    

    滚动数组的另一个重要且典型的应用在01背包中,这里就不再赘述.

  • 相关阅读:
    CF1202F You Are Given Some Letters...
    CF1178E Archaeology
    PTA (Advanced Level) 1005 Spell It Right
    PTA (Advanced Level) 1004 Counting Leaves
    Qt5——从零开始的Hello World教程(Qt Creator)
    PTA (Advanced Level) 1003 Emergency
    PTA (Advanced Level) 1002 A+B for Polynomials
    HDU 1272 小希的迷宫
    FZU 2150 Fire Game
    HihoCoder
  • 原文地址:https://www.cnblogs.com/i-cookie/p/11565277.html
Copyright © 2011-2022 走看看