问题:
https://www.acwing.com/activity/content/problem/content/1007/
分析:
该题的石子合并有明显的阶段性,不能使用贪心来做,只能使用区间DP
定义状态 f[i, j] 为合并 i 到 j 的所有石子的代价和,可以将状态划分为 f[i, k] + f[k + 1, j] + s[j] - s[i - 1] k ∈ [i, j - 1] 通过分组将 f[i, j] 的所有可能划分出来,其中 s[j] 表示 j 的前缀和
区间DP的问题,总是通过选定不同的起点,将所有的距离遍历。
代码:
/// 使用 for 循环实现
#include<iostream>
using namespace std ;
const int N = 310 ;
int n ;
int s[N], f[N][N] ;
int main()
{
cin >> n ;
// 前缀和从 1 开始计算,留出 0 的位置
for(int i = 1 ; i <= n ; i ++)
{
scanf("%d", &s[i]) ;
s[i] += s[i - 1] ;
}
for(int len = 2 ; len <= n ; len ++)
for(int i = 1 ; i + len - 1 <= n ; i ++)
{
int l = i, r = i + len - 1 ;
// 需要自身比较,设置较大初值
f[l][r] = 1e9 ;
for(int k = l ; k < i + len - 1 ; k ++)
f[l][r] = min(f[l][r], f[l][k] + f[k + 1][r] + s[r] - s[l - 1]) ;
}
cout << f[1][n] << endl ;
return 0 ;
}
// 记忆化搜索
#include<iostream>
#include<cstring>
using namespace std ;
const int N = 310 ;
int n ;
int s[N], f[N][N] ;
int dp(int i, int j)
{
if(i == j) return 0 ;
int &v = f[i][j] ;
if(v != -1) return f[i][j] ;
v = 1e9 ;
for(int k = i ; k < j ; k ++)
v = min(v, dp(i, k) + dp(k + 1, j) + s[j] - s[i - 1]) ;
return v ;
}
int main()
{
cin >> n ;
for(int i = 1 ; i <= n ; i ++)
{
scanf("%d", &s[i]) ;
s[i] += s[i - 1] ;
}
memset(f, -1, sizeof f) ;
int res = dp(1, n) ;
cout << res << endl ;
return 0 ;
}