区间DP-石子合并(线性)

时间:2022-06-16 08:14:39

石子合并(一)

时间限制:1000 ms  |  内存限制:65535 KB难度:3
描述
    有N堆石子排成一排,每堆石子有一定的数量。现要将N堆石子并成为一堆。合并的过程只能每次将相邻的两堆石子堆成一堆,每次合并花费的代价为这两堆石子的和,经过N-1次合并后成为一堆。求出总的代价最小值。
输入
有多组测试数据,输入到文件结束。
每组测试数据第一行有一个整数n,表示有n堆石子。
接下来的一行有n(0< n <200)个数,分别表示这n堆石子的数目,用空格隔开
输出
输出总代价的最小值,占单独的一行
样例输入
3
1 2 3
7
13 7 8 16 21 4 18
样例输出
9
239
来源

 

题目在:

Here

 

这是一道经典的区间DP,做出来也花了不少时间,具体注释见代码。这里要注意的是sum数组是一个前缀和数组,需要注意的一点就是sum[i]=a[0]+a[1]+...+a[i];

sum[j]=a[0]+a[1]+a[2]+...+a[j],那么显然sum[i~j]=sum[j]-sum[i]+a[i],即sum[i~j]=sum[j]-sum[i-1].不然会减去a[i]。。

还有就是记忆化搜索这块儿,题目说最终合并为一堆,那么当本来只有一堆的时候合并代价是0,我写成了num[i]。注意这几点便可以把这道问题解决了。

 

/****************************************************
* author:crazy_石头
* Pro:石子合并问题
* algorithm:区间DP
* Time:420ms
* Judge Status:Accepted
*******************************************************/
#pragma comment(linker, "/STACK:102400000,102400000")
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <queue>
#include <vector>
#include <algorithm>

using namespace std;

#define rep(i,h,n) for(int i=(h);i<=(n);i++)
#define ms(a,b) memset((a),(b),sizeof(a))
#define eps 1e-6
#define INF 1<<29
#define LL __int64
const int maxn=200+5;
const int maxm=1000000+10;

int dp[maxn][maxn];
int sum[maxn],num[maxn];

inline int dfs(int i,int j)
{
if(dp[i][j]!=INF)return dp[i][j];

if(i==j)return dp[i][j]=0;//题目要合并为一堆,如果本身是一堆,那么不用合并,即代价为0;
if(i+1==j)return dp[i][j]=num[i]+num[j];//两堆的话,合并代价等于这两堆的数量相加;
if(i>j)return dp[i][j]=0;
//其余情况,将大范围划小,则i~j间花费的费用等于i~k的代价加上k+1到j的代价并加上i~j间石子数目;
for(int k=i;k<j;k++)
dp[i][j]=min(dp[i][j],dfs(i,k)+dfs(k+1,j)+sum[j]-sum[i-1]);
return dp[i][j];
}

int main()
{
int test,n;
while(cin>>n)
{
int m;
ms(sum,0);
cin>>num[0];
sum[0]=num[0];
for(int i=1;i<n;i++)
{
cin>>num[i];
sum[i]=sum[i-1]+num[i];
}
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
dp[i][j]=INF;
}
}

int res=dfs(0,n-1);
printf("%d\n",res);
}
return 0;
}