石子问题(区间dp)

码蹄集OJ-石子合并

cpp 复制代码
#include<bits/stdc++.h> 
using namespace std;
const int N=202;
int n,a[N],sum[N],dp[N][N];
int main( )
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        a[i+n]=a[i];
    }
    for(int i=1;i<=2*n;i++)
    {
        sum[i]=sum[i-1]+a[i];
    }
    for(int i=1;i<=2*n;i++)
    {
        for(int j=1;j<=2*n;j++)
        {
            if(i==j)
            {
                dp[i][j]=0;
            }
            else
            {
                dp[i][j]=-1e8;
            }
        }
    }
    for(int len=2;len<=n;len++)
    {
        for(int i=1;i+len-1<=2*n;i++)
        {
            int j=i+len-1;
            for(int k=i;k<j;k++)
            {
                dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]);
            }
        }
    }
    int result=0;
    for(int i=1;i<=n;i++)
    {
        result=max(result,dp[i][i+n-1]);
    }
    cout<<result;
    return 0;
}

将数组的大小扩大一倍(即把原数组复制一份接在后面),将环形问题转换成线性问题解决。

任何环形区间的操作,都能在展开后的双倍线性数组上,通过一个连续的线性区间来表示。

定义dp[i][j]数组表示将i到j之一段石子合并成一堆时的得分。

初始化dp数组,在区间范围为0时,dp数组大小为0。由于题中求的是最小值,其他dp数组定义为无穷小。

枚举区间大小,再枚举起点,这样终点的值可以被确定。定义一个中间值k,遍历从起点到终点的所有值,得出状态转移方程:

cpp 复制代码
 dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]);

因为在石子问题中,总值=之前的值+当前的值,当前的值是确定的,通过前面算的累加数组得到起点到终点的值sum[j]-sum[i-1]。之前的值就通过枚举中间值k来表示。

注意:因为题目是环形数组,所以结果不能直接输出dp[1][j]。应该遍历所有起点,得出的最大值才是结果。