题意:
给定n堆石子,每次可以合并其中相邻的一些堆,合并的数目在区间[L,R]中,每次合并的花费是他们数量的和
思路:
明显的区间dp,但是有个点很巧
因为每次合并都要限定合并的个数,所以区间dp的时候要记录个数状态;
容易想到的是方程是:dp[i][j][k] 表示把 i 到 j 区间合并后,得到 k 堆石子的 最小花费,
唯一的转移的方式就是合并成一堆的时候,
其余的情况只是等式的转移 例如:dp[i][j][k] = min(dp[i][j][k], dp[i][t][x]+dp[t+1][j][k-x]) 原本的k堆石子并没有产生价值的转移
正常的想法是要枚举 区间两个端点 i,j 两层循环;得到的k堆 一层循环; 区间转移是中间某个值t 一层循环,区间 i-t 得到的x堆石子 一层循环,,一共五重循环,稳稳的炸。。。。。。。。。。
所以这个题的关键所在就是上述的x不需要枚举,把他当做1,因为在枚举 i j 的时候相应的各种情况都能包含到
这时候只需要4层循环,还要单独处理合并成一堆的情况,枚举t,由子区间的满足区间[L,R]的情况合并而来
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 100 + 7;
const ll INF = 0x3f3f3f3f;
int n, L, R;
ll a[maxn];
ll sum[maxn];
ll dp[maxn][maxn][maxn];
int main() {
while(~scanf("%d%d%d", &n, &L, &R) && n) {
sum[0] = 0;
for(int i = 1; i <= n; ++i) {
scanf("%lld", &a[i]);
sum[i] = sum[i-1] + a[i];
}
for(int i = 1; i <= n; ++i) {
for(int j = i; j <= n; ++j) {
for(int k = 1; k <= n; ++k) {
dp[i][j][k] = INF;
}
}
}
for(int i = 1; i <= n; ++i) {
dp[i][i][1] = 0;
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) { // [i , i+len-1] qujian
if(i+len-1 > n) break;
int j = i+len-1;
for(int t = i; t < j; ++t) {
for(int x = L-1; x < R; ++x) {
dp[i][j][1] = min(dp[i][j][1], dp[i][t][1]+dp[t+1][j][x]+(sum[j]-sum[i-1]));
}
}
for(int k = 2; k <= len; ++k) {
for(int t = i; t < j; ++t) {
if(j-t<k-1)break;
dp[i][j][k] = min(dp[i][j][k], dp[i][t][1]+dp[t+1][j][k-1]);
}
}
}
}
// cout << INF << endl;
printf("%lld\n", dp[1][n][1] == INF ? 0LL : dp[1][n][1]);
}
return 0;
}