排除错误思路
乍看一眼示例,可能会有这样的想法:每次找出和最小的两个相邻元素合并。这是贪心的思路,但其实是错误的。
看下面的示例:{ 3,3,2,4 }。
贪心法过程如下:第一次合并为{ 3,5,4 },代价5;第二次合并为{ 8,4 },代价8;第三次合并为{ 12 },代价12;总代价为5 + 8 + 12 = 25。
正确的合并过程如下:第一次合并为{ 6,2,4 },代价6;第二次合并为{ 6,6 },代价6;第三次合并为{ 12 },代价12;总代价为6 + 6 + 12 = 24。
此题正确的思路是动态规划,标题区间dp已经给出了明确的提示。
动态规划
我们使用分治的思想,一个大区间可以由两个小区间合并而成。令dp[l][r]表示将区间[l, r]的所有元素合并为1个元素的最小代价。那么有状态转移方程——
dp[l][r] = min(dp[l][l] + dp[l+1][r], ... , dp[l][k] + dp[k + 1][r], ... , dp[l][r-1] + dp[r][r]) + sum(l, r)
其中sum(l, r) 表示区间[l, r]的所有元素之和,也就是合并[l, r]两个子区间的代价。该值可以使用前缀和的思路来求解。
c++的AC代码如下,注意使用long long。
#include <climits> #include <iostream> #include <vector> using namespace std; int main() { int n; cin >> n; if (n == 1) { cout << 0; return 0; } vector<int> a(n + 1); vector<int> sum(n + 1); vector<vector<long long>> dp(n + 1, vector<long long>(n + 1, 0)); cin >> a[1]; sum[1] = a[1]; for (int i = 2; i <= n; ++i) { cin >> a[i]; dp[i - 1][i] = a[i - 1] + a[i]; sum[i] = sum[i - 1] + a[i]; } for (int i = 2; i < n; ++i) { for (int l = 1; l <= n - i; ++l) { int r = l + i; long long tmp = LLONG_MAX; for(int j = l; j < r; ++j) { tmp = min(tmp, dp[l][j] + dp[j + 1][r]); } dp[l][r] = tmp + sum[r] - sum[l - 1]; } } cout << dp[1][n]; } // 64 位输出请用 printf("%lld")