区间DP
- 环形 --> 链形
- 方案数
- 区间DP+高精度
- 二维区间DP
实现方式:
- 迭代式: recommended, especially in 1D
- 1: 枚举长度
for (int Len=1; Len<=n; Len++)
- 2: 枚举左端点
for (iny L=1; L+Len-1<=n; L++)
- 右端点:
R = L+Len-1
- 记忆化搜索
282 石子合并
设有 N 堆石子排成一排,其编号为 1,2,3,…,N
每堆石子有一定的质量,可以用一个整数来描述,现在要将这 N 堆石子合并成为一堆
每次只能合并相邻的两堆,合并的代价为这两堆石子的质量之和,合并后与这两堆石子相邻的石子将和新堆相邻,合并时由于选择的顺序不同,合并的总代价也不相同
例如有 4 堆石子分别为 1 3 5 2
, 我们可以先合并 1、2 堆,代价为 4,得到 4 5 2
, 又合并 1、2 堆,代价为 9,得到 9 2
,再合并得到 11,总代价为 4+9+11=24
如果第二步是先合并 2、3堆,则代价为 7,得到 4 7
,最后一次合并代价为 11,总代价为 4+7+11=22
问题是:找出一种合理的方法,使总的代价最小,输出最小代价
动态规划
- 状态表示:
f[i][j]
- 表示将 i 到 j 这一段石子合并成一堆的方案的集合
- 属性 Min
- 状态计算:
C++ |
---|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44 | #include <iostream>
#include <cstring>
using namespace std;
const int N = 307;
int a[N], s[N];
int f[N][N];
int main() {
int n;
cin >> n;
for (int i = 1; i <= n; i ++) {
cin >> a[i];
s[i] += s[i - 1] + a[i];
}
// 初始化 (求min, 因此init成MAX)
memset(f, 0x3f, sizeof f);
// 区间 DP 枚举套路: 长度+左端点
for (int len = 1; len <= n; len ++)
{ // len表示[i, j]的元素个数
for (int i = 1; i + len - 1 <= n; i ++)
{
int j = i + len - 1; // 自动得到右端点
if (len == 1) {
f[i][j] = 0; // 边界初始化
continue;
}
for (int k = i; k <= j - 1; k ++)
{ // 必须满足k + 1 <= j
f[i][j] = min(f[i][j], f[i][k] + f[k + 1][j] + s[j] - s[i - 1]);
}
}
}
cout << f[1][n] << endl;
return 0;
}
|
1068 环形石子合并
将 “环” 展成 “链” 的方法: n的环 -> 2n的链
将 n 堆石子绕圆形操场排放,现要将石子有序地合并成一堆。
规定每次只能选相邻的两堆合并成新的一堆,并将新的一堆的石子数记做该次合并的得分。
请编写一个程序,读入堆数 n 及每堆的石子数,并进行如下计算:
- 选择一种合并石子的方案,使得做 n−1 次合并得分总和最大
- 选择一种合并石子的方案,使得做 n−1 次合并得分总和最小
输入格式:
- 第一行包含整数 n,表示共有 n 堆石子
- 第二行包含 n 个整数,分别表示每堆石子的数量

C++ |
---|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53 | #include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 410, INF = 0x3f3f3f3f;
int n;
int w[N], s[N];
int f[N][N], g[N][N]; // f: DP求min; g: DP求max
int main()
{
cin >> n;
for (int i = 1; i <= n; i ++ )
{
cin >> w[i];
w[i + n] = w[i]; // 1~n + 1~n
}
// 前缀和预处理, 以求得 sum[i,j]
for (int i = 1; i <= n * 2; i ++ ) s[i] = s[i - 1] + w[i];
memset(f, 0x3f, sizeof f); // 求min, init成MAX
memset(g, -0x3f, sizeof g); // 求max, init成MIN
for (int len = 1; len <= n; len ++ )
for (int l = 1; l + len - 1 <= n * 2; l ++ )
{
int r = l + len - 1;
if (l == r) f[l][r] = g[l][r] = 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;
}
|
320 能量项链

比如:
- input:
2 3 5 10
- 珠子:
(2,3) (3,5) (5,10) (10,2)
- 改进:
(2,3,5,10,2)
扫描一遍即可
baseline: 动态规划 (线形)

动态规划 (环形)
将 “环” 展成 “链” 的方法: n的环 -> 2n的链
C++ |
---|
| f[L, R] = max (f[L, K] + f[K, R] + w[L]*w[K]*w[R])
|
代码:
C++ |
---|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37 | #include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 210, INF = 0x3f3f3f3f;
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]; // 1~n + 1~n
}
// 3: 最少3才能合并
// n+1: 回顾上面 2 3 5 10 -> 2 3 5 10 2
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 l = 1; l <= n; l ++ ) res = max(res, f[l][l + n]);
cout << res << endl;
return 0;
}
|
1069 凸多边形的划分
给定一个具有 N 个顶点的凸多边形,将顶点从 1 至 N 标号,每个顶点的权值都是一个正整数
将这个凸多边形划分成 N−2 个互不相交的三角形,对于每个三角形,其三个顶点的权值相乘都可得到一个权值乘积,试求所有三角形的顶点权值乘积之和至少为多少
出发点:

动态规划:

代码:

479 加分二叉树
区间DP 如何记录方案

分析:

动态规划:

需要记录方案, 等价于 存 f[L, R]
是从谁转移过来的. 因此需要开一个g[]
数组来存转移
g[L][R]
: 存 f[L][R]
的 根节点
C++ |
---|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52 | #include <iostream>
using namespace std;
const int N = 31;
int n;
int w[N];
int f[N][N];
int g[N][N]; // g(l, r)用于记录(l, r)区间内的根结点
void dfs(int left, int right)
{
if (left > right) return;
int root = g[left][right];
//前序遍历,根-左-右
cout << root << " ";
dfs(left, root - 1);
dfs(root + 1, right);
}
int main() {
cin >> n;
for (int i = 1; i <= n; ++i) cin >> w[i];
for (int i = 1; i <= n; ++i) f[i][i] = w[i], g[i][i] = i;
// 叶结点的权值是本身,这是题目规定的
// 初始化f和g
// 当然这个可以写进循环里,特判len==1的情况,我这样写是便于理解
for (int len = 2; len <= n; ++len)
{
for (int l = 1; l + len - 1 <= n; ++l)
{
int r = l + len - 1;
for (int k = l; k <= r; ++k)
{
int l_tr = k == l ? 1 : f[l][k - 1];
int r_tr = k == r ? 1 : f[k + 1][r];
int score = l_tr * r_tr + w[k];
if (score > f[l][r])
{
f[l][r] = score;
g[l][r] = k;
}
}
}
}
cout << f[1][n] << endl;
dfs(1, n);
return 0;
}
|
321 棋盘分割

二维区间DP + 记忆化搜索
数学分析: 目标

动态规划:

C++ |
---|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61 | #include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
const int N = 16;
int n, m = 8;
int s[N][N];
double f[N][N][N][N][N];
double X;
double get(int x1, int y1, int x2, int y2) //求该矩阵的 n\sigma^2
{
double delta = s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1];
delta = delta - X;
return delta * delta;
}
double dp(int k, int x1, int y1, int x2, int y2)
{
if (f[k][x1][y1][x2][y2] >= 0) return f[k][x1][y1][x2][y2]; // 记忆化搜索
if (k == n) return f[k][x1][y1][x2][y2] = get(x1, y1, x2, y2); // 更新初始状态
double t = 1e9; //初始化为无穷大
for (int i = x1; i < x2; i ++ ) //横着切
{
t = min(t, dp(k + 1, x1, y1, i, y2) + get(i + 1, y1, x2, y2));
t = min(t, dp(k + 1, i + 1, y1, x2, y2) + get(x1, y1, i, y2));
}
for (int i = y1; i < y2; i ++ ) //竖着切
{
t = min(t, dp(k + 1, x1, y1, x2, i) + get(x1, i + 1, x2, y2));
t = min(t, dp(k + 1, x1, i + 1, x2, y2) + get(x1, y1, x2, i));
}
return f[k][x1][y1][x2][y2] = t;
}
int main()
{
//读入
scanf("%d", &n);
for (int i = 1; i <= m; i ++ )
for (int j = 1; j <= m; j ++ )
scanf("%d", &s[i][j]);
//预处理前缀和
for (int i = 1; i <= m; i ++ )
for (int j = 1; j <= m; j ++ )
s[i][j] += s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1];
//初始化所有状态
memset(f, -1, sizeof f);
//计算均值X拔
X = (double) s[m][m] / n;
//记忆化搜索
printf("%.3lf\n", sqrt(dp(1, 1, 1, m, m) / n));
return 0;
}
|