本章面向区间动态规划展开
题目是从ACM训练内容上选用的,题面英文,内容较难
一本通上有几道区间dp的习题,模型比较类似,可以作为入门题

所谓区间问题,就是给出的问题是按区间的性质进行的,要么合并,要么分解为区间操作。《CCF中学生计算机程序设计》

区间DP的主要思想是先在小区间进行DP得到最优解,然后再利用小区间的最优解合并求大区间的最优解。《算法竞赛入门到进阶》

区间DP也属于线性DP中的一种,它以“区间长度”作为DP的“阶段”,使用两个坐标(区间的左、右端点)描述每个维度。在区间DP中,一个状态由若干个比它更小且包含于它的区间所代表的状态转移而来,因此,区间DP的决策往往就是划分区间的方法。区间DP的初态,一般由长度为1的“元区间”构成。这种向下划分、再向上递推的模式与某些树形结构,有很大的相似之处。《算法竞赛进阶指南》

区间类动态规划是线性动态规划的扩展,它在分阶段地划分问题时,与阶段中元素出现的顺序和由前一阶段的哪些元素合并而来有很大的关系。

  1. 令状态 f[i, j] 表示将下标位置 i j 的所有元素合并能获得的价值的最大值,那么
  2. f[i, j] = max{f(i, k) + f(k + 1, j) + cost}
  1. Nested Ranges Style
  2. (((a)(bc))(def))(ghijkl)
  3. Consecutive Ranges Style
  4. (ab)(cde)(fg)
  5. General Ranges Style
  6. 回文串

Nested Ranges Style

例题,合并石子

在一个操场上一排地摆放着N堆石子。现要将石子有次序地合并成一堆。规定每次只能选相邻的2堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的得分。计算出将N堆石子合并成一堆的最小得分。
image.png
image.png
例题,【例9.18】合并石子

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. const int N = 310;
  4. int a[N], s[N], n;
  5. int f[N][N]; // f[i][j]表示合并[i,j]区间的石子需要耗费的体力
  6. int main(){
  7. cin >> n;
  8. for (int i = 1; i <= n; i++){
  9. cin >> a[i];
  10. s[i] = s[i - 1] + a[i];
  11. }
  12. memset(f, 0x3f, sizeof f);
  13. for (int i = 1; i <= n; i++) f[i][i] = 0; // 自己一个的时候,不用合并,耗费是0
  14. for (int len = 2; len <= n; len++) // 阶段,有两个石子才发生合并的行为
  15. for (int i = 1; i <= n - len + 1; i++){ // 状态 左端点i
  16. int j = i + len - 1; // 状态 右端点j
  17. for (int k = i; k < j; k++)
  18. f[i][j] = min(f[i][j], f[i][k] + f[k + 1][j]);
  19. f[i][j] += s[j] - s[i - 1];
  20. }
  21. cout << f[1][n] << '\n';
  22. return 0;
  23. }

例题,CasketOfStar

image.png
image.png
image.png

  1. // my_code, cannot submit to Topcoder
  2. #include <bits/stdc++.h>
  3. using namespace std;
  4. const int N = 55;
  5. int weight[N], mem[N][N];
  6. int n;
  7. int best(int i, int j)
  8. {
  9. if (j - i + 1 <= 2) return 0;
  10. if (j - i + 1 == 3) return weight[i] * weight[j];
  11. int &ret = mem[i][j];
  12. if (ret != -1) return ret;
  13. for (int k = i + 1; k < j; k++)
  14. ret = max(ret, best(i, k) + best(k, j) + weight[i] * weight[j]);
  15. return ret;
  16. }
  17. int main()
  18. {
  19. cin >> n;
  20. for (int i = 1; i <= n; i++) cin >> weight[i];
  21. memset(mem, -1, sizeof mem);
  22. cout << best(1, n) << '\n';
  23. return 0;
  24. }

例题,Cutting Sticks

image.png

  1. // Cutting Sticks,请上机练习
  2. // 代码比较类似

例题,Optimal Array Multiplication Sequence

image.png

  1. // 难度:提高+/省选-
  2. // 需要递归输出方案
  3. // 请上机操作,课后自行展开,提高代码能力

Consecutive Ranges Style

例题,Pearls2a846e1562af749877f30ee62b0463f6 (1).jpg

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. const int N = 110, INF = 0x3f3f3f3f;
  4. int a[N], p[N], n, T;
  5. int mem[N][N];
  6. int best(int u, int last)
  7. {
  8. if (u == n + 1) return 0;
  9. int &ret = mem[u][last];
  10. if (ret != INF) return ret;
  11. int sum = 10;
  12. for (int i = last; i <= u; i++) sum += a[i];
  13. //buy now, add to right now interval
  14. ret = min(ret, sum * p[u] + best(u + 1, u + 1));
  15. if (u != n) ret = min(ret, best(u + 1, last));
  16. return ret;
  17. }
  18. int main()
  19. {
  20. cin >> T;
  21. while (T--){
  22. cin >> n;
  23. for (int i = 1; i <= n; i++) cin >> a[i] >> p[i];
  24. memset(mem, 0x3f, sizeof mem);
  25. printf("%d\n", best(1, 1));
  26. }
  27. return 0;
  28. }
  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. const int N = 110, INF = 0x3f3f3f3f;
  4. int a[N], p[N], n, T;
  5. int mem[N][N];
  6. int s[N];
  7. int best(int u, int last)
  8. {
  9. if (u == n + 1) return 0;
  10. int &ret = mem[u][last];
  11. if (ret != INF) return ret;
  12. int sum = 10 + s[u] - s[last - 1];
  13. //buy now, add to right now interval
  14. ret = min(ret, sum * p[u] + best(u + 1, u + 1));
  15. if (u != n) ret = min(ret, best(u + 1, last));
  16. return ret;
  17. }
  18. int main()
  19. {
  20. cin >> T;
  21. while (T--){
  22. cin >> n;
  23. for (int i = 1; i <= n; i++) cin >> a[i] >> p[i];
  24. memset(s, 0, sizeof s);
  25. for (int i = 1; i <= n; i++) s[i] += s[i - 1] + a[i];
  26. memset(mem, 0x3f, sizeof mem);
  27. printf("%d\n", best(1, 1));
  28. }
  29. return 0;
  30. }
  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. const int N = 110, INF = 0x3f3f3f3f;
  4. int a[N], p[N], n, T, s[N];
  5. int dp[N];
  6. int main()
  7. {
  8. cin >> T;
  9. while (T--){
  10. memset(s, 0, sizeof s);
  11. memset(dp, 0x3f, sizeof dp);
  12. cin >> n;
  13. for (int i = 1; i <= n; i++) cin >> a[i] >> p[i];
  14. for (int i = 1; i <= n; i++) s[i] += s[i - 1] + a[i];
  15. dp[0] = 0;
  16. for (int i = 1; i <= n; i++)
  17. for (int j = 1; j <= i; j++)
  18. dp[i] = min(dp[i], dp[j - 1] + (s[i] - s[j - 1] + 10) * p[i]);
  19. cout << dp[n] << '\n';
  20. }
  21. return 0;
  22. }

General Ranges Style

例题,String to Palindrome

image.png

  1. // 递归写法,my 10739_1.cpp
  2. # include<vector>
  3. # include<string>
  4. # include<iostream>
  5. using namespace std;
  6. string A, B;
  7. const int MAX = 1000+9;
  8. int memo[MAX][MAX];
  9. int best(int i, int j)
  10. {
  11. if(i >= j)
  12. return 0;
  13. int &ret = memo[i][j];
  14. if(ret != -1)
  15. return ret;
  16. ret = best(i+1, j-1) + (A[i] != A[j]);
  17. ret = min(ret, best(i+1, j)+1);
  18. ret = min(ret, best(i, j-1)+1);
  19. return ret;
  20. }
  21. int main() {
  22. int num;
  23. cin >> num;
  24. for (int i = 1; i <= num; i++) {
  25. cin >> A;
  26. memset(memo, -1, sizeof memo);
  27. cout << "Case " << i << ": " << best(0, (int)A.size()-1) << endl;
  28. }
  29. return 0;
  30. }
  1. // 循环写法
  2. #include<iostream>
  3. #include<string>
  4. using namespace std;
  5. int min(int a, int b, int c)
  6. {
  7. if(b < a) a = b;
  8. if(c < a) a = c;
  9. return a;
  10. }
  11. int A[1001][1001]; /* The cost of substring A[i][j] */
  12. int main()
  13. {
  14. int i, j, k, len, cases, diag;
  15. string str;
  16. cin>>cases;
  17. for (k = 1; k <= cases; k++)
  18. {
  19. cin>>str;
  20. for (i=0; i<str.size(); i++) /* Base case in diagonal */
  21. A[i][i] = 0; /* Empty string */
  22. for (len = 2; len <= str.size(); len++) /* O(n^2) */
  23. { /* when len = 2 --> Special case */
  24. for (i = 0; i+len<=str.size(); i++)
  25. {
  26. j = i + len - 1, diag = 0;
  27. if (str[i] != str[j])
  28. diag = 1;
  29. if( i+1 <= j-1 ) /* at least j-i > 1 */
  30. A[i][j] = min(A[i+1][j]+1, A[i][j-1]+1, A[i+1][j-1]+diag);
  31. else /* Undefined Stage (Not in base case) i = j+1 */
  32. A[i][j] = diag; /* j=i+1Tow char string */
  33. }
  34. }
  35. cout<<"Case "<<k<<": "<<A[0][str.size()-1]<< "\n";
  36. }
  37. return 0;
  38. }
  39. // by zqiceberg
  40. #include <bits/stdc++.h>
  41. using namespace std;
  42. const int N = 1010;
  43. int T, idx;
  44. int dp[N][N];
  45. char s[N];
  46. int main()
  47. {
  48. cin >> T;
  49. while (T--){
  50. scanf("%s", s);
  51. int LEN = strlen(s);
  52. for (int i = 0; i < LEN; i++) dp[i][i] = 0;
  53. for (int len = 2; len <= LEN; len++)
  54. for (int i = 0; i + len <= LEN; i++){
  55. int j = i + len - 1;
  56. int flag = 0;
  57. if (s[i] != s[j]) flag = 1;
  58. if (i + 1 <= j - 1){
  59. dp[i][j] = dp[i + 1][j - 1] + flag;
  60. dp[i][j] = min(dp[i][j], dp[i + 1][j] + 1);
  61. dp[i][j] = min(dp[i][j], dp[i][j - 1] + 1);
  62. }
  63. else dp[i][j] = flag;
  64. }
  65. printf("Case %d: %d\n", ++idx, dp[0][LEN - 1]);
  66. }
  67. return 0;
  68. }

例题,Cheapest Palindrome

  1. #include <iostream>
  2. #include <cstdio>
  3. using namespace std;
  4. const int N = 2010;
  5. char s[N];
  6. int w[N], dp[N][N]; // dp[i][j]表示 子区间[i...j]变成回文的最小花费
  7. int n, m;
  8. int main(){
  9. cin >> n >> m;
  10. scanf("%s", s);
  11. for (int i = 0; i < n; i++){
  12. char c;
  13. int x, y;
  14. cin >> c >> x >> y;
  15. w[c - 'a'] = min(x, y);
  16. }
  17. // 枚举的区间是从小区间扩展到大区间
  18. for (int i = m - 1; i >= 0; i--)
  19. for (int j = i + 1; j < m; j++){
  20. if (s[i] == s[j]){
  21. dp[i][j] = dp[i + 1][j - 1];
  22. }
  23. else{
  24. dp[i][j] = min(dp[i + 1][j] + w[s[i] - 'a'],
  25. dp[i][j - 1] + w[s[j] - 'a']);
  26. }
  27. }
  28. cout << dp[0][m - 1] << '\n';
  29. return 0;
  30. }

例题,【例9.20】编辑距离

  1. //Tips: 对比一下最短编辑距离

例题代码

  1. // 例题,Cutting Sticks
  2. #include <bits/stdc++.h>
  3. using namespace std;
  4. const int N = 55;
  5. int a[N], l, n;
  6. int mem[N][N];
  7. int best(int i, int j)
  8. {
  9. if (j - i <= 1) return 0;
  10. if (j - i == 2) return a[j] - a[i];
  11. int &ret = mem[i][j];
  12. if (ret != 0x3f3f3f3f) return ret;
  13. for (int k = i + 1; k < j; k++){
  14. ret = min(ret, best(i, k) + best(k, j) + a[j] - a[i]);
  15. }
  16. return ret;
  17. }
  18. int main()
  19. {
  20. while (cin >> l){
  21. if (l == 0) break;
  22. cin >> n;
  23. for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
  24. a[0] = 0, a[n + 1] = l;
  25. memset(mem, 0x3f, sizeof mem);
  26. printf("The minimum cutting is %d.\n", best(0, n + 1));
  27. }
  28. return 0;
  29. }
  1. // 例题,Optimal Array Multiplication Sequence
  2. #include <bits/stdc++.h>
  3. using namespace std;
  4. typedef pair<int, int> PII;
  5. const int N = 20, INF = 0x3f3f3f3f;
  6. PII a[N];
  7. int path[N][N];
  8. int n, idx;
  9. int mem[N][N];
  10. void print(int i, int j)
  11. {
  12. if (j == i){
  13. printf("A%d", i);
  14. return ;
  15. }
  16. if (j - i == 1){
  17. printf("(A%d x A%d)", i, j);
  18. return ;
  19. }
  20. int k = path[i][j];
  21. printf("(");
  22. print(i, k);
  23. printf(" x ");
  24. print(k + 1, j);
  25. printf(")");
  26. }
  27. int best(int i, int j)
  28. {
  29. if (j - i == 0) return 0;
  30. if (j - i == 1) return mem[i][j] = a[i].first * a[i].second * a[j].second;
  31. if (mem[i][j] != INF) return mem[i][j];
  32. int &ret = mem[i][j];
  33. for (int k = i; k < j; k++){
  34. int t = best(i, k) + best(k + 1, j) + a[i].first * a[k].second * a[j].second;
  35. if (t < ret){
  36. ret = t;
  37. path[i][j] = k;
  38. }
  39. }
  40. return ret;
  41. }
  42. int main()
  43. {
  44. while (cin >> n && n){
  45. idx++;
  46. for (int i = 1; i <= n; i++){
  47. cin >> a[i].first >> a[i].second;
  48. }
  49. memset(mem, 0x3f, sizeof mem);
  50. best(1, n);
  51. printf("Case %d: ", idx);
  52. print(1, n);
  53. puts("");
  54. }
  55. return 0;
  56. }