例题1:石子合并(弱化版)
区间DP合并 模板
题目描述
设有 N ( N ≤ 300 ) N(N \le 300) N(N≤300) 堆石子排成一排,其编号为 1 , 2 , 3 , ⋯ , N 1,2,3,\cdots,N 1,2,3,⋯,N。每堆石子有一定的质量 m i ( m i ≤ 1000 ) m_i\ (m_i \le 1000) mi (mi≤1000)。现在要将这 N N N 堆石子合并成为一堆。每次只能合并相邻的两堆,合并的代价为这两堆石子的质量之和,合并后与这两堆石子相邻的石子将和新堆相邻。合并时由于选择的顺序不同,合并的总代价也不相同。试找出一种合理的方法,使总的代价最小,并输出最小代价。
输入格式
第一行,一个整数 N N N。
第二行, N N N 个整数 m i m_i mi。
输出格式
输出文件仅一个整数,也就是最小代价。
样例 #1
样例输入 #1
4
2 5 3 1
样例输出 #1
22
解决思路
对于区间DP来说,有明显的合并特征的题型,一般是在先枚举区间长度 L ,在枚举右端点 i ,通过右端点以及长度来的到左端点 j ,最后枚举中间节点 k 。
当然对于这道题目,是需要求区间的和,可以使用前缀和优化。
状态
设dp[i][j] 表示 合并 区间 [ i , j ] 的 代价。
pre[i] 表示前 i 个数的前缀和
转移
dp[i][j] = max(dp[i][j] , dp[i][k] + dp[k+1][j] + pre[j] - pre[i-1])
代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 305;
int dp[N][N];
int a[N],pre[N];
int n;
int main()
{
cin >> n;
for(int i=1;i<=n;i++) cin >> a[i];
memset(dp,127,sizeof(dp)); // 初始化为最小
for(int i=1;i<=n;i++) dp[i][i] = 0;
pre[1] = a[1];
for(int i=2;i<=n;i++) pre[i] = pre[i-1] + a[i]; // 前缀和优化
for(int l=2;l<=n;l++){ // 因为以及特殊处理了长度为 1 的各个数 所以长度从 2 开始
for(int i=1;i+l-1<=n;i++){
int j = i+l-1;
for(int k=i;k<=j;k++){ // 枚举中间点 k
dp[i][j] = min(dp[i][j],dp[i][k]+dp[k+1][j]+ pre[j]-pre[i-1]);
}
}
}
cout << dp[1][n];
return 0;
}
例题2 [NOI1995] 石子合并
区间DP 断环成链 模板
题目描述
在一个圆形操场的四周摆放 N N N 堆石子,现要将石子有次序地合并成一堆,规定每次只能选相邻的 2 2 2 堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。
试设计出一个算法,计算出将 N N N 堆石子合并成 1 1 1 堆的最小得分和最大得分。
输入格式
数据的第 1 1 1 行是正整数 N N N,表示有 N N N 堆石子。
第 2 2 2 行有 N N N 个整数,第 i i i 个整数 a i a_i ai 表示第 i i i 堆石子的个数。
输出格式
输出共 2 2 2 行,第 1 1 1 行为最小得分,第 2 2 2 行为最大得分。
样例 #1
样例输入 #1
4
4 5 9 4
样例输出 #1
43
54
提示
1 ≤ N ≤ 100 1\leq N\leq 100 1≤N≤100, 0 ≤ a i ≤ 20 0\leq a_i\leq 20 0≤ai≤20。
解决思路
因为这是一个换,就可以采用 断环成链 的思想解题。具体实现就是将 a 数组存两份,
所以不管如何移动区间都可以满足情况。
对于这道题目,可以同时记录最大和最小
注:不能直接输出dp[1][n],因为不知道是保留哪条边来的。最后进行枚举得出答案
代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 300;
int n;
int a[N];
int pre[N];
int dp1[N][N];
int dp2[N][N];
int main()
{
cin >> n;
for(int i = 1; i <= n; i++){
cin >> a[i];
a[i+n] = a[i]; // 断环成链 存两次
}
n<<=1; // 改变 n 的大小
memset(dp1,127,sizeof(dp1));memset(dp2,128,sizeof(dp2));
// 初始化 一个最大 一个最小
for(int i = 1; i <= n; i++){
pre[i] = pre[i-1] + a[i]; // 仍然进行前缀和优化
dp1[i][i] = 0;dp2[i][i] = 0; // 合并本身的代价为0
}
for(int l = 2; l <= n ; l++){
for(int i = 1; i+l-1 <=n; i++){
int j = i+l-1;
for(int k = i; k < j; k++){
dp1[i][j] = min(dp1[i][j],dp1[i][k]+dp1[k+1][j]+pre[j]-pre[i-1]);
dp2[i][j] = max(dp2[i][j],dp2[i][k]+dp2[k+1][j]+pre[j]-pre[i-1]);
}
}
}
int mx = INT_MIN,mi = INT_MAX;
n>>=1;
// 将 n 还原枚举 [1,n]区间最大最小值
for(int i=1;i<=n;i++){
mi = min(mi,dp1[i][i+n-1]);
mx = max(mx,dp2[i][i+n-1]);
}
cout << mi << endl << mx;
return 0;
}