【解题报告】 [NOI1995]石子合并

这道题应该说还是有一定的来头的毕竟作为区间DP的鼻祖

首先让我们先来介绍一下什么叫做区间DP:

区间DP顾名思义就是在一段区间上的动态规划。它既要满足dp问题的最优子结构和无后效性外,还应该符合在区间上操作的特点。所以说区间DP往往会对区间进行合并操作。可看成一个小区间(通过多次的相邻合并,最后实质上会产生跨区间的合并)。其实自己感觉这个玩意儿挺像倍增的。

那么下面我们来讲一讲例题:

题目链接:
P1880 [NOI1995]石子合并

一句话题意:

给你一个环状的数组,然后每次我们把两个数合并在一起,消耗值是他们两个数之和,问最后合并成一堆的时候最小的消耗值是多少,其实后来这道题想一想也觉得并不是那么的难,其实最难想到的并不是区间DP,而是我们如何处理这个环的问题。其实没想到不是笨,还是见的题目太少了。我们可以把这个环拆开,拆成一个链,然后我们把两个同样的链连接起来,这样我们最后寻找最优解我们只需要在这两条链上比较一下从每一个点开始的当前链长度哪个解更优!

说不太清楚,大家看一下代码应该就明白了!

    for(register int i=1;i<=n;++i)
    {
        minx=min(minx,dp_min[i][i+n-1]);//刷表找最小,把环拆开! 
        maxx=max(maxx,dp_max[i][i+n-1]);// 
    }

然后其实根据刚才我们所讲的区间dp思想,我们其实只要把动态规划数组的一维设成左区间端点二维设成右区间端点进行dp就可以了(从小到大进行合并)

具体代码如下:

for(register int i=(n<<1)-1;i>0;--i)  
    //for(int i=1;i<(n<<1);++i)
    {
        for(register int j=i+1;j<i+n;++j) 
        {
            dp_min[i][j]=inf;
            for(register int k=i;k<j;++k)
            {
                dp_min[i][j]=min(dp_min[i][j],dp_min[i][k]+dp_min[k+1][j]+sum[j]-sum[i-1]);  //脑洞DP 
                dp_max[i][j]=max(dp_max[i][j],dp_max[i][k]+dp_max[k+1][j]+sum[j]-sum[i-1]);  //代价还要加上这次合并后的石子数量  
            }
        } 
    }

注意:这里表一定要倒着刷否则前面刷的表会被后面刷的表覆盖,然后就出锅了!

上面更正一下:

其实这样打更保险:

    //for(register int i=(n<<1)-1;i>0;--i)  
    for(register int j=1;j<=n;++j)
    {
        //for(register int j=i+1;j<i+n;++j) 
        for(register int s=1;s+j<=n*2;++s)
        {
            int e=s+j;
            dp_min[s][e]=inf;
            for(register int k=s;k<e;++k)
            {
                dp_min[s][e]=min(dp_min[s][e],dp_min[s][k]+dp_min[k+1][e]+sum[e]-sum[s-1]);  //脑洞DP 
                dp_max[s][e]=max(dp_max[s][e],dp_max[s][k]+dp_max[k+1][e]+sum[e]-sum[s-1]);  //代价还要加上这次合并后的石子数量  
            }
        } 
    }

我们从小区间逐渐往大区间进行合并,这样不容易错,并且刷表也不可能刷重复。

关键部分相信大家应该都懂了,那么应该就可以完成了!

参考代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn=505,inf=0x3f3f3f3f;
int n,num[maxn*2],sum[maxn*2],dp_min[maxn][maxn*2],dp_max[maxn][maxn*2];
int minx=inf,maxx;
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;++i)
    scanf("%d",&num[i]),num[i+n]=num[i];
    for(int i=1;i<=2*n;++i)
    sum[i]=sum[i-1]+num[i];
    //for(register int i=(n<<1)-1;i>0;--i)  
    for(register int j=1;j<=n;++j)
    {
        //for(register int j=i+1;j<i+n;++j) 
        for(register int s=1;s+j<=n*2;++s)
        {
            int e=s+j;
            dp_min[s][e]=inf;
            for(register int k=s;k<e;++k)
            {
                dp_min[s][e]=min(dp_min[s][e],dp_min[s][k]+dp_min[k+1][e]+sum[e]-sum[s-1]);  //脑洞DP 
                dp_max[s][e]=max(dp_max[s][e],dp_max[s][k]+dp_max[k+1][e]+sum[e]-sum[s-1]);  //代价还要加上这次合并后的石子数量  
            }
        } 
    }
    for(register int i=1;i<=n;++i)
    {
        minx=min(minx,dp_min[i][i+n-1]);//刷表找最小,把环拆开! 
        maxx=max(maxx,dp_max[i][i+n-1]);// 
    }
    printf("%d\n%d\n",minx,maxx);
    return 0;
}

一定要记住,我们要从小区间逐渐往大区间合并。
By njc

posted @ 2020-07-17 12:06  Mudrobot  阅读(159)  评论(0)    收藏  举报