转载https://blog.csdn.net/tigerisland45/article/details/61622152
这是一个编码问题,似乎可以用哈夫曼编码来解决,但是略有不同的地方在于“每个字符的编码按照字典序排列后的顺序与原先顺序一样”。
所以无法每次取出权值最小的两个节点,而只能选择相邻的节点,到底选择哪两个相邻节点,这便是石子问题
设dp[i][j]表示第i到第j堆石子合并的最优值,sum[i][j]表示第i到第j堆石子的总数量。那么就有状态转移公式:
1、dp[i][j]=0 (i==j)
2、dp[i][j]=min(dp[i][k]+dp[k][j])+sum[i][j] (i!=j)
此时算法复杂为O(n^3)。
这里可以利用平行四边形优化降为O(n^2):
由上面的方程式可知我们每次求dp[i][j]的关键是找到合适的k值,设p[i][j]为dp[i][j]的这个合适的k值,根据平行四边形规则有以下不等式:p[i][j-1]<=p[i][j]<=p[i+1][j]。
那么求解dp[i][i+L](L为长度)的复杂度就为:
(p[2,L+1]-p[1,L])+(p[3,L+2]-p[2,L+1])…+(p[n-L+1,n]-p[n-L,n-1])=p[n-L+1,n]-p[1,L]≤n。
复杂度为O(n)。然后L从1循环至n,总复杂度就为O(n^2)。
程序说明。:程序中,INT_MAX2值是个将就的做法,并不是整数最大值。
这里给出了两个代码程序,后一种做了优化,速度快了非常多。前一个程序运行时间是秒级的,后一个程序运行时间是毫秒级的。
/* CCF201612-4 压缩编码 */
#include <iostream>
#include <cstring>
using namespace std;
const int INT_MAX2 = 0x7F7F7F7F;
const int N = 1000;
int v[N+1];
int sum[N+1];
int dp[N+1][N+1];
int solve(int l, int r)
{
if(dp[l][r] == INT_MAX2)
for(int i = l; i < r; i++)
dp[l][r] = min(dp[l][r], solve(l, i) + solve(i+1, r) + sum[r] - sum[l-1]);
return dp[l][r];
}
int main()
{
int n;
// 变量初始化:置为最大值
memset(dp, INT_MAX2, sizeof(dp));
// 输入数据
cin >> n;
sum[0] = 0;
for(int i=1; i<=n; i++) {
cin >> v[i];
sum[i] = sum[i-1] + v[i];
dp[i][i] = 0;
}
// DP计算
solve(1, n);
// 输出结果
cout << dp[1][n] << endl;
return 0;
}