P1775 石子合并(弱化版)
感觉 dp 太难了,这真的感觉太难学了,但是还要写题记积累啊,唉!
感觉不用讲题意了(那你也别讲题解了)就是石子之间可以合并,合并的代价是这堆石子数,问如何合并全部石子后总代价最小。
考虑用区间 dp,设状态为 \(dp[i][j]\) 为区间 \([i,j]\) 的最小代价,转移时先枚举区间长度,然后枚举区间右端点,一个长度大于等于2的区间可以分为左右小区间,所以再枚举中间点分为左右区间,得到转移方程:
\(dp[j][r]=min(dp[j][r],dp[j][k]+dp[k+1][r]+s[j][r]);\)
最后答案就是 \(dp[1][n]\)。
合并代价就是区间和,所以可以用前缀和优化。
#include<bits/stdc++.h>
using namespace std;
int n; // 序列长度
int dp[305][305]; // 动态规划表,dp[i][j]表示将区间[i, j]合并为一段的最小代价
int s[305]; // 前缀和数组,s[i]表示前i个元素的和,用于快速计算区间和
int a[305]; // 存储输入的序列
int main(){
ios::sync_with_stdio(false); // 加速输入输出,避免C++的流式IO同步
cin >> n; // 读取序列长度
memset(dp,0x3f,sizeof dp); // 初始化动态规划表,0x3f3f3f3f是一个很大的数,相当于无穷大
for(int i = 1; i <= n; i++){
cin >> a[i]; // 读取序列中的元素
dp[i][i] = 0; // 初始化dp表,当区间[i, i]只有一个元素时,合并代价为0
s[i] = s[i-1] + a[i]; // 计算前缀和,用于快速获取任意区间的元素和
}
// 动态规划部分
for(int len = 2; len <= n; len++){ // len是当前处理的子区间长度,逐渐从2增加到n
for(int i = 1; i <= n - len + 1; i++){ // i是子区间的起始位置
int r = i + len - 1; // r是子区间的结束位置
for(int k = i; k < r; k++){ // 枚举分割点k,将[i, r]分成[i, k]和[k+1, r]两部分
dp[i][r] = min(dp[i][r], dp[i][k] + dp[k+1][r] + s[r] - s[i-1]);
// 合并代价为子区间[i, k]和[k+1, r]的合并代价之和,再加上区间[i, r]的总和
}
}
}
cout << dp[1][n]; // 输出整个序列[1, n]的最小合并代价
return 0;
}