NC51170 石子合并

经典的区间\(DP\)问题。

区间\(DP\)特征:从小区间逐渐向大区间扩展递推。

题解

贪心只能处理“任取两堆”,而不能处理“相邻两堆”。任取两堆的题目就是合并果子

状态表示:\(f(i,j)\):合并区间\([i,j]\)的最小代价
状态转移:\(f(i,j) = \begin{cases}0,i = j\\ \underset{i≤k<j}{min}(f(i,j),f(i,k)+f(k+1,j)+sum(i,j)),i≠j \end{cases}\)
\(sum(i,j)\)为区间\([i,j]\)内石子的个数。

相当于将\([i,j]\)拆成两个区间\([i,k]\)\([k+1,j]\),两个区间各自合并石子需要\(f(i,k)\)
\(f(k+1,j)\)的代价,将两个区间合并需要\(sum(i,j)\)的代价。

\(\color{red}{Wrong Answer}\)代码:

const int N=310;
int f[N][N];
int a[N];
int sum[N];
int n;
 
int main()
{
    cin>>n;
 
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        sum[i]=sum[i-1]+a[i];
    }
 
    memset(f,0x3f,sizeof f);
 
    for(int i=1;i<=n;i++)
        for(int j=i;j<=n;j++)
        {
            if(i == j) f[i][j]=0;
            else
                for(int k=i;k<j;k++)
                    f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+sum[j]-sum[i-1]);
        }
 
    cout<<f[1][n]<<endl;
    //system("pause");
}

在考虑如何递推时,应考虑如下几个方面:

  • 是否能覆盖全部状态?
  • 求解后面状态时是否保证前面状态已经确定?
  • 是否修改了已经确定的状态?

也就是说,在考虑递推顺序时,务必参考动态规划的对象具有的性质。

既然之前说过我们需要枚举k来划分i和j,那么如果通过枚举i和j进行状态转移,很显然某些k值时并不能保证已经确定过所需状态。

  • \(i=1 ~ 10\)
  • \(j=1 ~ 10\)
  • \(k=1 ~ 9\)

\(i=1,j=5,k=3\)时,显然状态\(f(k+1,j)\)没有计算。

解决办法也很简单,将\(i\)倒序循环即可。
\(\color{green}{Accepted}\)代码:

const int N=310;
int f[N][N];
int a[N];
int sum[N];
int n;

int main()
{
    cin>>n;

    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        sum[i]=sum[i-1]+a[i];
    }

    memset(f,0x3f,sizeof f);

    for(int i=n;i>=1;i--)
        for(int j=i;j<=n;j++)
        {
            if(i == j) f[i][j]=0;
            else
                for(int k=i;k<j;k++)
                    f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+sum[j]-sum[i-1]);
        }

    cout<<f[1][n]<<endl;
    //system("pause");
}
posted @ 2020-12-15 20:38  Dazzling!  阅读(81)  评论(0编辑  收藏  举报