AcWing 1068. 环形石子合并(区间dp)

AcWing 1068. 环形石子合并(区间dp)_第1张图片
AcWing 1068. 环形石子合并(区间dp)_第2张图片

题意:

给定 n 个石子的 分数 wi,且石子是 环形放置 的(即:在给定的顺序上,同时满足 1n 也是相邻的)

规定每次只能合并 相邻 的两堆石子,形成一个新的石子堆,合并的 费用 是两个石子堆的 分数之和

求解两个方案:

  • 方案一:把这 n 堆石子合并为一堆,且满足费用最大的方案

  • 方案二:把这 n 堆石子合并为一堆,且满足费用最小的方案

思路:

本题是限制只能合并 相邻两堆石子的模型,是区间dp模板题:AcWing 282. 石子合并 的环形扩展问题。

考虑如何设定 动态规划 的阶段,既可以表示出 初始 每个石子的费用,也可以表示出 合并后 整个堆的费用

我们考虑将 当前合并的石子堆的大小 作为DP的阶段,这样 len=1 表示初值,即每个堆只有一个石子len=n 表示终值,即一个堆中有所有的石子

这种阶段设置方法能够保证:每次合并两个区间时,它们所有的子区间合并费用都已经被计算出来了

阶段设定好后,我们考虑如何记录当前的状态,无外乎两个参数:

  • 石子堆的左端点 l
  • 石子堆的右端点 r

DP分析
状态表示—集合 f l e n , l , r flen,l,r flen,l,r :当前合并的石子堆的大小为 l e n len len,且石子堆的左端点是 l l l,右端点是 r r r 的方案
状态表示—属性 f l e n , l , r flen,l,r flen,l,r: 方案的费用最大/最小(本题两者都要求)
状态计算— f l e n , l , r flen,l,r flen,l,r:

{计算最大值的转移:flen,l,r=max(fk−l+1,l,k+flen−(k−l+1),k+1,r+costl,r)(l≤k<r)计算最小值的转移:flen,l,r=min(fk−l+1,l,k+flen−(k−l+1),k+1,r+costl,r)(l≤k<r)
在这里插入图片描述
注意:

考虑一下本题的 环形相邻 情况如何解决,方案有如下两种:

  • 枚举环中分开的位置,将环还原成链,这样就需要枚举 n 次,时间复杂度为 O(n^4)
  • 把链延长两倍,变成 2n 个堆,其中 ii+n 是相同的两个堆,然后直接套 区间DP 模板,但对于 阶段 len 只枚举到 n,根据 状态的定义,最终可以得到所求的方案,时间复杂度为 O(n^3)

初始状态 f 1 , i , i f1,i,i f1,i,i( 1 ≤ i ≤ n 1≤i≤n 1in)

目标状态 f n , 1 , n fn,1,n fn,1,n

代码:

#include

using namespace std;

const int N = 210, M = N<<1, inf = 0x3f3f3f3f;
int n;
int f[M][M], g[M][M];
int s[M], w[M];

int main()
{
    cin>>n;
    for(int i=1; i<=n; ++i)
    {
        cin>>w[i];
        w[i+n] = w[i];
    }
    //预处理前缀和(DP状态转移中用到的区间和)
    for(int i=1; i<=n<<1; ++i)
    {
        s[i] = s[i-1] + w[i];
    }
    
    memset(f, 0x3f, sizeof f);
    memset(g, -0x3f, sizeof g);
    for(int len = 1; len <= n; ++len)//阶段:区间长度
    {
        for(int l = 1; l + len - 1 <= n<<1; ++l)//左端点
        {
            int r = l + len -1;//右端点
            if(len==1) {f[l][r] = 0, g[l][r] = 0; continue;}//区间长度为1时特殊判断
            for(int k = l; k < r; ++k)//枚举分界点
            {
                f[l][r] = min(f[l][r], f[l][k] + f[k+1][r] + s[r] - s[l-1]);
                g[l][r] = max(g[l][r], g[l][k] + g[k+1][r] + s[r] - s[l-1]);
            }
        }
    }
    //目标状态中找出方案
    int MIN = inf, MAX = -inf;
    for(int i=1; i<=n; ++i)
    {
        MIN = min(MIN, f[i][i+n-1]);
        MAX = max(MAX, g[i][i+n-1]);
    }
    
    cout<

你可能感兴趣的:(区间dp,动态规划,动态规划,算法,c++)