区间DP
石子合并
方法:区间DP
想法
首先想到贪心
但是由于只能合并相邻的石子,因此不能做到极致地贪最小,换句话说,合并哪个是不确定的,所以不能用贪心
还有什么方法?
DFS枚举每次合并?
但是时间复杂度不允许,因此我们考虑DP
如何DP?
状态转移方程?
考虑l到r以k为分割点的所有的合并方法
i到k的最小代价 + k+1到j的最小代价 + i到j的区间和
得出状态转移方程:
\(f(i, j) = min{f(i, k) + f(k+1, j) + s[j] - s[i - 1], f(i, j)}\)
答案如何表示?
答案:1到n石子合并的最小代价
根据集合的定义可知:f[1, n]即为答案
注意
要通过枚举区间长度len来确定l和r
原因: 更长的区间状态需要从更短的区间转移过来,所以需要先枚举更短的区间
完整代码
#include <iostream>
#include <cstring>
using namespace std;
const int N = 310;
int s[N], f[N][N], n;
int main()
{
scanf("%d", &n);
memset(f, 0x3f3f3f3f, sizeof f); // 由于要求的是最小值,所以初始化INF是正的
for(int i = 1; i <= n; i++)
{
scanf("%d", &s[i]);
s[i] += s[i-1]; //前缀和
f[i][i] = 0; // 长度是1的区间代价自然是0,因为不用合并
}
for(int len = 2; len <= n; len++) // 从2开始,长度为1的区间已经确定
{
for(int l = 1; l + len - 1 <= n; l++) // 区间左
{
int r = l + len - 1; // 知道l和len,r也就确定了
for(int k = l; k < r; k++) // 中间点
f[l][r] = min(f[l][r], s[r] - s[l - 1] + f[l][k] + f[k+1][r]);
}
}
printf("%d", f[1][n]);
return 0;
}
环状区间DP
题目描述
将 \(n\) 堆石子绕圆形操场排放,现要将石子有序地合并成一堆。规定每次只能选相邻的两堆合并成新的一堆,并将新的一堆的石子数记做该次合并的得分。
请编写一个程序,读入堆数 \(n(1≤n≤200)\)。 及每堆的石子数,并进行如下计算:
1、选择一种合并石子的方案,使得做 \(n-1\) 次合并得分总和最大。
2、选择一种合并石子的方案,使得做 \(n-1\) 次合并得分总和最小。
输入
输入第一行一个整数 \(n\),表示有 \(n\) 堆石子。
第二行 \(n\) 个整数,表示每堆石子的数量。
输出
输出共两行:
第一行为合并得分总和最小值,
第二行为合并得分总和最大值。
基本思路
环状区间DP其实与普通的区间DP并无太大区别,唯一的差别在于其从线性的变成了环状的,其特点在于可以循环取数对于这种情况
对于这种情况,我们可以断环成链
将记录石子数量的数组\(a[N]\)的范围倍长,变为\(a[2N]\),而后将\(a[1\sim n]\)映射至\(a[n+1 \sim 2n]\),仔细观察发现此时数组也具备环的属性,直接套普通的区间DP即可
#include <iostream>
#include <cstring>
#include <algorithm>
#define INF 0x3f3f3f3f
using namespace std;
const int N = 210;
typedef long long LL;
int a[N], s[2 * N], f1[N][N], f2[N][N];
int main()
{
int n;
cin >> n;
for(int i = 1; i <= n; i ++)
{
cin >> a[i];
a[i + n] = a[i]; // 断环成链!
}
for(int i = 1; i <= 2 * n; i ++)
s[i] = s[i - 1] + a[i]; // 计算前缀和数组
for(int len = 2; len <= n; len ++)
{
for(int i = 1; i + len - 1 <= 2 * n; i ++)
{
int j = i + len - 1;
f1[i][j] = 0; // max
f2[i][j] = INF; // min
for(int k = i; k < j; k ++)
{
f1[i][j] = max(f1[i][j], f1[i][k] + f1[k + 1][j] + s[j] - s[i - 1]);
f2[i][j] = min(f2[i][j], f2[i][k] + f2[k + 1][j] + s[j] - s[i - 1]);
}
}
}
int mx = -INF, mn = INF;
for(int i = 1; i <= n; i ++)
{
mx = max(mx, f1[i][i + n - 1]);
mn = min(mn, f2[i][i + n - 1]);
}
cout << mn << endl << mx << endl;
return 0;
}
拓展例题
\(\color{green} AC 代码\)
#include <iostream>
#include <cstring>
#include <algorithm>
#define INF 0x3f3f3f3f
using namespace std;
const int N = 1100;
typedef long long LL;
int a[2 * N], f[N][N];
int main()
{
int n;
cin >> n;
for(int i = 1; i <= n; i ++)
{
cin >> a[i];
a[i + n] = a[i];
}
for(int len = 2; len <= n + 1; len ++)
{
for(int i = 1; i + len - 1 <= 2 * n; i++)
{
int j = i + len - 1;
for(int k = i + 1; k <= j - 1; k ++)
{
f[i][j] = max(f[i][j], f[i][k] + f[k][j] + a[i] * a[j] * a[k]);
}
}
}
int mx = -INF;
for(int i = 1; i <= n; i++)
{
mx = max(mx, f[i][i + n]);
}
cout << mx << endl;
return 0;
}