排除错误思路
乍看一眼示例,可能会有这样的想法:每次找出和最小的两个相邻元素合并。这是贪心的思路,但其实是错误的。
看下面的示例:{ 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")



京公网安备 11010502036488号