区间DP 更新到石子合并

区间DP

【个人理解】

我觉得所有的DP都是优化的枚举(可能学的少,至少线性DP我觉得是),把一开始的状态结果保存到到数组中,然后推导后面的状态。我觉得区间DP同理,也是一个由短区间推导长区间的一个过程。最典型的例子就是下面的合并石子。

【经典例题】

石子合并有三种类型,

第一种任意位置合并,那个简单贪心做,就行,例题:洛谷 P1090 合并果子

第二种直线性相邻合并,对应着下面的第一个。

第二种环形下的相邻合并,对应这第二个。

石子合并(一)(直线相邻)

时间限制:1000 ms  |  内存限制:65535 KB

难度:3

描述 

N堆石子排成一排,每堆石子有一定的数量。现要将N堆石子并成为一堆。合并的过程只能每次将相邻的两堆石子堆成一堆,每次合并花费的代价为这两堆石子的和,经过N-1次合并后成为一堆。求出总的代价最小值。

输入

有多组测试数据,输入到文件结束。
每组测试数据第一行有一个整数n,表示有n堆石子。
接下来的一行有n0< n <200)个数,分别表示这n堆石子的数目,用空格隔开

输出

输出总代价的最小值,占单独的一行

样例输入

3

1 2 3

7

13 7 8 16 21 4 18

样例输出

9

239

 

分析:

如果我们可以把第l堆石子和第r堆石子合并起来,则说明第l堆石子和第r堆石子之间的每一堆都被合并了,这样l和r才能相邻,所以,任意时刻,任意一堆石子均可以用闭区间[l,r]表示,表示这堆石子是由第l~r堆石子合并而来,重量为a[l]+a[l+1]+……a[r].还有,一定存在一个整数k(l<=k<r),在这堆石子之前先有l~k堆石子被合并成一堆,然后k+1~r堆石子被合并成一堆,然后这两堆石子合并成一堆。

从上述分析,我们就可

以得到一个对应的动态规划的性质,两个长度较小的区间信息向一个更长的区间发生了转移,划分点k就产生了转移决策。自然的,我们要把区间长度len先由小到大枚举(高深点就是把区间长度作为阶段),保证后面的长区间由短区间得到,然后我们应该枚举l(左端点),并找到相应的右端点l+len-1(对应着状态),枚举中间断点k,f[l][r]表示第l堆合并到第r堆合成一堆,需要消耗的最小力气。

对应的状态转移方程:  f[l][r]=min(f[l][r],f[l][k]+f[k+1][r])+ a[l]+a[l+1]+……a[r]

a[l]+a[l+1]+……a[r]= sum[r]-sum[l-1])用一个前缀和来维护就行,或许你不知道为什么把l~r的石子连加起来就对了,不是应该两个合并有一个保存值吗?

例如,1,2,3.正确应该为 1+2=3 ,3+3=6,第一次合并3,第二次合并6,答案是九

而上述为什么只计算前缀和1+2+3=6,但你别忘了f[i][j]本身就是有值的,它是有区间长度为2开始一点点推出来的。

状态转移方程  f[l][r]=min(f[l][r],f[l][k]+f[k+1][r])+ sum[r]-sum[l-1])

初值:f[i][i]=0,其余为无穷大

目标:f[1][n]

代码实现:

#include <cstdio>
#include <queue>
#include <cstring>
#include <algorithm>
using namespace std;
#define mst(a,b) memset((a),(b),sizeof(a))
 
typedef long long ll;
const int maxn = 205;
const ll mod = 1e9+7;
const ll INF = 1e18;
const double eps = 1e-9;
 
int n,x;
int sum[maxn];
int dp[maxn][maxn];
 
int main()
{
    while(~scanf("%d",&n))
    {
        sum[0]=0;
        mst(dp,0x3f);     //初始化
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&x);
            sum[i]=sum[i-1]+x; //前缀和
            dp[i][i]=0;   //初始化
        }
        for(int len=2;len<=n;len++) //阶段:区间长度
        for(int i=1;i<=n;i++)     //状态:左端点
        {
            int j=i+len-1;    //状态:右端点
            if(j>n) continue;  //决策
            for(int k=i;k<j;k++)
            {
                dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]);
            }
        }
        printf("%d\n",dp[1][n]);
    }
    return 0;
}

 

石子合并(二)(环形相邻)

题目:洛谷 P1880 [NOI1995]石子合并

这题与上面有什么不同呢,一个简单例子:这题的最后一个和第一个一开始就能合并,也就是说它的摆放是一个环状,而上面那一题是一排。

由于是环状,我们需要把它转换为链状,那么就把存储石子的数组a[]空间开大一倍,从n+1~2*n存储的值等于1~n存储的值,那么只需要从1n枚举链的开头即可,链尾则分别为n2*n-1。这样计算和环状是等效的。解决:

举个例子:

假如现在叫你合并1 2 3,我们就可以写出1 2 3 1 2 3,也就是说把数组扩大成一倍,这样你只要保证你选出来的数是三个就行了,你试试看。

然后你按照上面的思想做就

猜你喜欢

转载自blog.csdn.net/sdz20172133/article/details/81780364