434
社区成员
发帖
与我相关
我的任务
分享设有 N堆石子排成一排,其编号为 1,2,3,…,N。
每堆石子有一定的质量,可以用一个整数来描述。用数组a[N]表示每堆石子的质量,sum[i,j]表示第i堆石子到第j堆石子的总质量
现在要将这 N堆石子合并成为一堆。每次只能合并相邻的两堆,合并的代价为这两堆石子的质量之和,合并后与这两堆石子相邻的石子将和新堆相邻,合并时由于选择的顺序不同,合并的总代价也不相同。
例如有 4堆石子分别为
1 3 5 2, 我们可以先合并 1、2 堆,代价为 4,得到4 5 2, 又合并 1,2 堆,代价为 9,得到9 2,再合并得到 11,总代价为 4+9+11=24;如果第二步是先合并 2,3 堆,则代价为 7,得到
4 7,最后一次合并代价为 11,总代价为 4+7+11=22。问题是:找出一种合理的方法,使总的代价最小,输出最小代价。
首先 N堆石子,可以看作2份N/2堆的石子各自合并的问题,再将2份石子合并一次得解,符合重叠子问题性质。
其次,原问题N堆石子的解取决于2份N/2堆石子的合并问题的最优解,符合最优子结构。
则我们可以分析出:
状态表示:dp[i][j] 表示第i堆石子到第j堆石子合并的最小总代价;
状态方程:dp[i][j] = min( dp[i][j] , dp[i][k] + dp[k+1][j] + sum[j] - sum[i-1] ) 1<=i<k<j<=N;//sum为前缀和数组辅助;
边界条件:dp[i][j]=0 i = j ;
时间复杂度:O(n^3)
空间复杂度:O(n^2)
附上代码:
#include<iostream>
using namespace std;
int dp[10010][10010];
int sum[10010];
int stone[10010];
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>stone[i];
sum[i]=sum[i-1]+stone[i];
}
for(int len=1;len<=n;len++)
{
for(int i=1;i+len-1<=n;i++)
{
int j=i+len-1;
if(len==1)
dp[i][j]=0;
else
{
for(int k=i;k<j;k++)
{
if(dp[i][j]!=0)
dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]);
else
dp[i][j]=dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1];
}
}
}
}
cout<<dp[1][n]<<endl;
return 0;
}
动态规划——总结起来就是比分治法应用更为广泛,比贪心算法更为全面,代码量不多,但是状态量难定义,状态转移方程较难推导,边界条件需要分析。
动态规划的问题一般满足下面条件:
问题是否具有重叠子问题性质和最优子结构;
根据问题推论出状态定义和转移方程;
最后判断一些数据大小,大部分的二维数组的dp可能会受到内存的限制,考虑是否能简化成一维数组进行处理;
动态规划也有许多的变形,合理的运用前缀和数组。