区间DP
1. 区间DP
定义
- 所谓区间
DP
是指在定义状态的时候定义了一个区间,我们根据区间长度len
由小到大逐步递推。
2. AcWing上的区间DP题目
AcWing 282. 石子合并
问题描述
-
问题链接:AcWing 282. 石子合并
分析
- 分析如下(
s
表示前缀和数组):
代码
- C++
#include <iostream>
using namespace std;
const int N = 310;
int n;
int s[N];
int f[N][N];
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) scanf("%d", &s[i]);
for (int i = 1; i <= n; i++) s[i] += s[i - 1];
for (int len = 2; len <= n; len++)
for (int i = 1; i + len - 1 <= n; i++) {
int j = i + len - 1;
f[i][j] = 1e8;
for (int k = i; k < j; k++)
f[i][j] = min(f[i][j], f[i][k] + f[k + 1][j] + s[j] - s[i - 1]);
}
printf("%d\n", f[1][n]);
return 0;
}
AcWing 1068. 环形石子合并
问题描述
-
问题链接:AcWing 1068. 环形石子合并
分析
- 本题使用到的技巧是:破环成链。可以将链断开,然后将整条链复制一遍,求出所有长度为
n
的区间合并所需的代价就是最后的结果。如下图是四个点破环成链的结果:
- 之后的分析和AcWing 282. 石子合并一样。
代码
- C++
#include <iostream>
#include <cstring>
using namespace std;
const int N = 410, INF = 0x3f3f3f3f;
int n;
int w[N], s[N]; // s前缀和
int f[N][N], g[N][N]; // f存储最小值,g存储最大值
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> w[i];
w[i + n] = w[i];
}
for (int i = 1; i <= n * 2; i++) s[i] = s[i - 1] + w[i];
memset(f, 0x3f, sizeof f);
memset(g, -0x3f, sizeof g);
for (int len = 1; len <= n; len++)
for (int l = 1; l + len - 1 <= 2 * n; l++) {
int r = l + len - 1;
if (len == 1) f[l][l] = g[l][l] = 0;
else {
for (int k = l; k < r; k++) {
f[l][r] = min(f[l][r], f[l][k] + f[k + 1][r] + s[r] - s[l - 1]);
g[l][r] = max(g[l][r], g[l][k] + g[k + 1][r] + s[r] - s[l - 1]);
}
}
}
int minv = INF, maxv = -INF;
for (int i = 1; i <= n; i++) {
minv = min(minv, f[i][i + n - 1]);
maxv = max(maxv, g[i][i + n - 1]);
}
cout << minv << endl << maxv << endl;
return 0;
}
AcWing 320. 能量项链
问题描述
-
问题链接:AcWing 320. 能量项链
分析
-
本题就是一个矩阵相乘问题。问怎样计算计算量最大,输出这个计算量。
-
为了方便计算,将输入最后补上第一个数,例如
2、3、5、10
变为2、3、5、10、2
。首先分析如果不是环,是一个线性相乘,则分析如下:
- 之后按照破环成链的做法即可,这里需要枚举长度是
n+1
的链(分别对应从每个点断开的结果),如下图是四个矩阵破环成链的结果。
代码
- C++
#include <iostream>
using namespace std;
const int N = 210;
int n;
int w[N];
int f[N][N];
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> w[i];
w[i + n] = w[i];
}
for (int len = 3; len <= n + 1; len++)
for (int l = 1; l + len - 1 <= n * 2; l++) {
int r = l + len - 1;
for (int k = l + 1; k < r; k++)
f[l][r] = max(f[l][r], f[l][k] + f[k][r] + w[l] * w[k] * w[r]);
}
int res = 0;
for (int i = 1; i <= n; i++) res = max(res, f[i][i + n]);
cout << res << endl;
return 0;
}
AcWing 479. 加分二叉树
问题描述
-
问题链接:AcWing 479. 加分二叉树
分析
- 分析如下:
- 记录方案可以使用另外一个二维数组
g
。g[l][r]=k
,代表[l, r]
这段区间的根节点应该选择k
。
代码
- C++
#include <iostream>
using namespace std;
const int N = 35;
int n;
int w[N], f[N][N], g[N][N];
void dfs(int l, int r) {
if (l > r) return;
int k = g[l][r];
cout << k << ' ';
dfs(l, k - 1), dfs(k + 1, r);
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> w[i];
for (int len = 1; len <= n; len++)
for (int i = 1; i + len - 1 <= n; i++) {
int j = i + len - 1;
if (len == 1) f[i][j] = w[i], g[i][j] = i;
else {
for (int k = i; k <= j; k++) {
int left = (k == i ? 1 : f[i][k - 1]);
int right = (k == j ? 1 : f[k + 1][j]);
int score = w[k] + left * right;
if (score > f[i][j]) {
f[i][j] = score;
g[i][j] = k;
}
}
}
}
cout << f[1][n] << endl;
dfs(1, n);
return 0;
}
AcWing 1069. 凸多边形的划分
问题描述
-
问题链接:AcWing 1069. 凸多边形的划分
分析
- 分析如下:
-
因为本题中的顶点最大为 1 0 9 10^9 109,三个数相乘会超出
long long
的范围,因此需要使用高精度。 -
本题不是环形
DP
问题,因为从哪一条边枚举结果最终都是一样的,因此随便选择一种枚举方式即可。
代码
- C++
#include <iostream>
#include <cstring>
using namespace std;
typedef long long LL;
const int N = 55, M = 35, INF = 1e9;
int n;
int w[N];
LL f[N][N][M]; // 最后一维存储高精度f[][][0]存储的是低位
void add(LL a[], LL b[]) {
static LL c[M];
memset(c, 0, sizeof c);
for (int i = 0, t = 0; i < M; i++) {
t += a[i] + b[i];
c[i] = t % 10;
t /= 10;
}
memcpy(a, c, sizeof c);
}
void mul(LL a[], LL b) {
static LL c[M];
memset(c, 0, sizeof c);
LL t = 0;
for (int i = 0; i < M; i++) {
t += a[i] * b;
c[i] = t % 10;
t /= 10;
}
memcpy(a, c, sizeof c);
}
int cmp(LL a[], LL b[]) {
for (int i = M - 1; ~i; i--)
if (a[i] > b[i]) return 1;
else if (a[i] < b[i]) return -1;
return 0;
}
void print(LL a[]) {
int k = M - 1;
while (k && a[k] == 0) k--;
while (k >= 0) cout << a[k--];
cout << endl;
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> w[i];
LL temp[M];
for (int len = 3; len <= n; len++)
for (int l = 1; l + len - 1 <= n; l++) {
int r = l + len - 1;
f[l][r][M - 1] = 1;
for (int k = l + 1; k < r; k++) {
memset(temp, 0, sizeof temp);
temp[0] = w[l];
mul(temp, w[k]);
mul(temp, w[r]);
add(temp, f[l][k]);
add(temp, f[k][r]);
if (cmp(f[l][r], temp) > 0)
memcpy(f[l][r], temp, sizeof temp);
}
}
print(f[1][n]);
return 0;
}
AcWing 321. 棋盘分割
问题描述
-
问题链接:AcWing 321. 棋盘分割
分析
- 分析如下:
代码
- C++
#include <iostream>
#include <cstring>
#include <cmath>
using namespace std;
const int N = 15, M = 9;
const double INF = 1e9;
int n, m = 8;
double s[M][M];
double f[M][M][M][M][N];
double X;
double get_sum(int x1, int y1, int x2, int y2) {
return s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1];
}
double get(int x1, int y1, int x2, int y2) {
double sum = get_sum(x1, y1, x2, y2) - X;
return sum * sum / n;
}
double dp(int x1, int y1, int x2, int y2, int k) {
auto &v = f[x1][y1][x2][y2][k];
if (v >= 0) return v;
if (k == 1) return get(x1, y1, x2, y2);
v = INF;
// 横切
for (int i = x1; i < x2; i++) {
v = min(v, dp(x1, y1, i, y2, k - 1) + get(i + 1, y1, x2, y2)); // 选上面继续切分
v = min(v, dp(i + 1, y1, x2, y2, k - 1) + get(x1, y1, i, y2)); // 选下面继续切分
}
// 纵切
for (int i = y1; i < y2; i++) {
v = min(v, dp(x1, y1, x2, i, k - 1) + get(x1, i + 1, x2, y2)); // 选左边继续切分
v = min(v, dp(x1, i + 1, x2, y2, k - 1) + get(x1, y1, x2, i)); // 选上面继续切分
}
return v;
}
int main() {
cin >> n;
for (int i = 1; i <= m; i++)
for (int j = 1; j <= m; j++) {
cin >> s[i][j];
s[i][j] += s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1];
}
X = s[m][m] / n;
memset(f, -1, sizeof f);
printf("%.3lf\n", sqrt(dp(1, 1, m, m, n)));
return 0;
}