题目链接:
题意:
有 n 堆石子,每堆有 a[i] 个,给定L,R,相邻的连续 [L,R] 堆石子可以合并成一堆,代价是这些堆中石子数量之和。求把 n 堆石子合并成一堆的最小代价,情况不存在输出 0 。
思路:
若L=R=2,则就是传统的区间dp:
dp[i][j] = min(dp[i][j],dp[i][k]+dp[k+1][j]+sum[i...j]) (i<=k<j)
dp[i][j] 表示第 i 堆石子到第 j 堆石子合并成一堆的最小代价。
现在多了一个限制条件,L,R不确定。所以我们在原先 dp 数组中再添一维。
dp[i][j][k] 表示第 i 堆石子到第 j 堆石子合并成 k 堆的最小代价。
k=1时:dp[i][j][1] = min(dp[i][j][1],dp[i][t][x-1]+dp[t+1][j][1]+sum[i...j]) (1<=t<j,L<=x<=R)
k>1时:dp[i][j][k] = min(dp[i][j][k],dp[i][t][k-1]+dp[t+1][j][1]) (1<=t<j,2<=k<=t-i+2(即k-1要小于等于子区间[i,t]的长度))
(两者计算顺序没有关系,彼此都不会用到相关数据)
初始化:由于要求最小值,所以需要先把 dp 数组初始化为 inf 。
然后,把 k 堆石子分成 k 堆的代价为 0 (相当于不分),即 dp[i][j][j-i+1]=0 。
Tips:计算 k>1 的情况时不需要满足 L<=k<=R 的性质。因为这些情况都是由 k=1 的情况转移而来,并且在 k=1 的时候已经限制好了( dp[i][j][1]=inf 时 ,该情况不会再发生转移 )。因此这些状态都是合法状态。
Code:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAX = 100 + 10;
const ll inf = 1e18;
int n, l, r;
ll a[MAX];
ll sum[MAX];
ll dp[MAX][MAX][MAX];
int main()
{
while (scanf("%d%d%d", &n, &l, &r) != EOF)
{
for (int i = 1; i <= n; i++) {
scanf("%lld", &a[i]);
}
for (int i = 1; i <= n; i++) {
sum[i] = sum[i - 1] + a[i];
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
for (int k = 1; k <= n; k++) {
dp[i][j][k] = inf;
}
}
}
for (int i = 1; i <= n; i++) {
for (int j = i; j <= n; j++) {
dp[i][j][j - i + 1] = 0;
}
}
for (int len = 2; len <= n; len++) {
for (int i = 1; i <= n; i++) {
int j = i + len - 1;
if (j > n) break;
for (int t = i; t < j; t++) {
for (int k = l; k <= r; k++) {
dp[i][j][1] = min(dp[i][j][1], dp[i][t][k - 1] + dp[t + 1][j][1] + sum[j] - sum[i - 1]);
}
}
for (int t = i; t < j; t++) {
for (int k = 2; k <= t - i + 2; k++) {
dp[i][j][k] = min(dp[i][j][k], dp[i][t][k - 1] + dp[t + 1][j][1]);
}
}
}
}
ll ans = dp[1][n][1];
if (ans == inf) {
printf("0\n");
}
else {
printf("%lld\n", ans);
}
}
return 0;
}
/*
5 3 3
1 2 1 3 5
5 2 2
1 2 1 3 5
*/