区间dp模型(石子归并,括号匹配,整数划分)

缺乏、安全感 2022-08-26 15:26 278阅读 0赞

区间dp顾名思义就是在一个区间上进行的一系列动态规划。对一些经典的区间dp总结在这里。

1) 石子归并问题

题目链接:http://acm.nyist.edu.cn/JudgeOnline/problem.php?pid=737

描述:有N堆石子排成一排,每堆石子有一定的数量。现要将N堆石子并成为一堆。合并的过程只能每次将相邻的两堆石子堆成一堆,每次合并花费的代价为这两堆石子的和,经过N-1次合并后成为一堆。求出总的代价最小值。

分析:要求n个石子归并,我们根据dp的思想划分成子问题,先求出每两个合并的最小代价,然后每三个的最小代价,依次知道n个。

定义状态dp [ i ] [ j ]为从第i个石子到第j个石子的合并最小代价。

那么dp [ i ] [ j ] = min(dp [ i ] [ k ] + dp [ k+1 ] [ j ])

那么我们就可以从小到大依次枚举让石子合并,直到所有的石子都合并。

这个问题可以用到平行四边形优化,用一个s【i】【j】=k 表示区间 i—-j 从k点分开才是最优的,这样的话我们就可以优化掉一层复杂度,变为O(n^2).

代码:

  1. #include <cstdio>
  2. #include <cstring>
  3. #include <algorithm>
  4. #define N 210
  5. int dp[N][N],sum[N];
  6. int main()
  7. {
  8. int n;
  9. while(~scanf("%d",&n))
  10. {
  11. int a[N];sum[0]=0;
  12. for(int i=1;i<=n;i++){
  13. scanf("%d",&a[i]);
  14. sum[i]=sum[i-1]+a[i];
  15. }
  16. memset(dp,0,sizeof(dp));
  17. int i,j,l,k;
  18. for(l = 2; l <= n; ++l)
  19. {
  20. for(i = 1; i <= n - l + 1; ++i)
  21. {
  22. j = i + l - 1;
  23. dp[i][j] = 2100000000;
  24. for(k = i; k < j; ++k)
  25. {
  26. dp[i][j] = std::min(dp[i][j],dp[i][k] + dp[k + 1][j] + sum[j] - sum[i-1]);
  27. }
  28. }
  29. }
  30. printf("%d\n", dp[1][n]);
  31. }
  32. return 0;
  33. }

平行四边形优化代码:

  1. #include <cstdio>
  2. #include <cstring>
  3. #include <algorithm>
  4. #define N 210
  5. int dp[N][N],sum[N],s[N][N];
  6. int main()
  7. {
  8. int n;
  9. while(~scanf("%d",&n))
  10. {
  11. int a[N];sum[0]=0;
  12. memset(s,0,sizeof(s));
  13. for(int i=1;i<=n;i++){
  14. scanf("%d",&a[i]);
  15. s[i][i]=i;
  16. sum[i]=sum[i-1]+a[i];
  17. }
  18. memset(dp,0,sizeof(dp));
  19. int i,j,l,k;
  20. for(l = 2; l <= n; ++l)
  21. {
  22. for(i = 1; i <= n - l + 1; ++i)
  23. {
  24. j = i + l - 1;
  25. dp[i][j] = 2100000000;
  26. for(k = s[i][j-1]; k <= s[i+1][j]; ++k)
  27. {
  28. if(dp[i][j]>dp[i][k] + dp[k + 1][j] + sum[j] - sum[i-1])
  29. {
  30. dp[i][j]=dp[i][k] + dp[k + 1][j] + sum[j] - sum[i-1];
  31. s[i][j]=k;
  32. }
  33. }
  34. }
  35. }
  36. printf("%d\n", dp[1][n]);
  37. }
  38. return 0;
  39. }

2)括号匹配

题目链接:

poj2955,http://poj.org/problem?id=2955

nyoj 15 http://acm.nyist.edu.cn/JudgeOnline/problem.php?pid=15

描述:给出一串的只有‘(’ ‘)’ ‘[‘ ‘]‘四种括号组成的串,让你求解需要最少添加括号数让串中的所有括号完全匹配。

分析:我们求出这个串的最大匹配,然后串的总长度-最大匹配就是答案。

方法1:首先能想到的是转化成LCS(最长公共子序列),枚举中间点,求所有的LCS中的最大值 * 2就是最大匹配。但是复杂度较高,光LCS一次就O(n^2)的复杂度。

方法2:

首先考虑怎么样定义dp让它满足具有通过子结构来求解、

定义dp [ i ] [ j ] 为串中第 i 个到第 j 个括号的最大匹配数目

那么我们假如知道了 i 到 j 区间的最大匹配,那么i+1到 j+1区间的是不是就可以很简单的得到。

那么 假如第 i 个和第 j 个是一对匹配的括号那么dp [ i ] [ j ] = dp [ i+1 ] [ j-1 ] + 2 ;

那么我们只需要从小到大枚举所有 i 和 j 中间的括号数目,然后满足匹配就用上面式子dp,然后每次更新dp [ i ] [ j ]为最大值即可。

更新最大值的方法是枚举 i 和 j 的中间值,然后让 dp[ i ] [ j ] = max ( dp [ i ] [ j ] , dp [ i ] [ f ] + dp [ f+1 ] [ j ] ) ;

如果要求打印路径,即输出匹配后的括号。见http://blog.csdn.net/y990041769/article/details/24238547详细讲解

代码:

  1. #include <iostream>
  2. #include <cstring>
  3. #include <algorithm>
  4. #include <string>
  5. using namespace std;
  6. const int N = 120;
  7. int dp[N][N];
  8. int main()
  9. {
  10. string s;
  11. while(cin>>s)
  12. {
  13. if(s=="end")
  14. break;
  15. memset(dp,0,sizeof(dp));
  16. for(int i=1;i<s.size();i++)
  17. {
  18. for(int j=0,k=i;k<s.size();j++,k++)
  19. {
  20. if(s[j]=='('&&s[k]==')' || s[j]=='['&&s[k]==']')
  21. dp[j][k]=dp[j+1][k-1]+2;
  22. for(int f=j;f<k;f++)
  23. dp[j][k]=max(dp[j][k],dp[j][f]+dp[f+1][k]);
  24. }
  25. }
  26. cout<<dp[0][s.size()-1]<<endl;
  27. }
  28. return 0;
  29. }

3)整数划分问题

题目链接:nyoj746 http://acm.nyist.net/JudgeOnline/problem.php?pid=746

题目描述:给出两个整数 n , m ,要求在 n 中加入m - 1 个乘号,将n分成m段,求出这m段的最大乘积

分析:根据区间dp的思想,我们定义dp [ i ] [ j ]为从开始到 i 中加入 j 个乘号得到的最大值。

那么我们可以依次计算加入1——m-1个乘号的结果

而每次放入x个乘号的最大值只需枚举第x个乘号的放的位置即可

dp [ i ] [ j ] = MAX (dp [ i ] [ j ] , dp [ k ] [ j-1 ] * a [ k+1 ] [ i ] ) ;

代码:

  1. #include <cstdio>
  2. #include <cstring>
  3. #define MAX(a,b) a>b?a:b
  4. long long a[20][20];
  5. long long dp[25][25];
  6. int main()
  7. {
  8. int T,m;
  9. scanf("%d",&T);
  10. getchar();
  11. while(T--)
  12. {
  13. char s[22];
  14. scanf("%s",s+1);
  15. scanf("%d",&m);
  16. int l=strlen(s),ok=1;
  17. memset(a,0,sizeof(a));
  18. for(int i=1;i<l;i++)
  19. {
  20. if(s[i]=='0')
  21. ok=0;
  22. for(int j=i;j<l;j++)
  23. {
  24. a[i][j]=a[i][j-1]*10+(s[j]-'0');
  25. }
  26. }
  27. if(ok==0&&l-1==m||l-1<m)
  28. {
  29. printf("0\n");continue;
  30. }
  31. long long x,ans;
  32. memset(dp,0,sizeof(dp));
  33. for(int i=0;i<l;i++)
  34. dp[i][1]=a[1][i];
  35. ans=0;
  36. if(m==1)
  37. ans=dp[l-1][1];
  38. for(int j=2;j<=m;j++)
  39. {
  40. for(int i=j;i<l;i++)
  41. {
  42. ans=a[i][i];
  43. for(int k=1;k<i;k++)
  44. {
  45. dp[i][j]=MAX(dp[i][j],dp[k][j-1]*a[k+1][i]);
  46. }
  47. }
  48. }
  49. printf("%lld\n",dp[l-1][m]);
  50. }
  51. return 0;
  52. }

发表评论

表情:
评论列表 (有 0 条评论,278人围观)

还没有评论,来说两句吧...

相关阅读

    相关 石子合并问题 (区间dp

    石子合并问题是最经典的DP问题。首先它有如下3种题型: (1)有N堆石子,现要将石子有序的合并成一堆,规定如下:每次只能移动任意的2堆石子合并,合并花费为新合成的

    相关 282 石子合并(区间dp

    1. 问题描述: 设有 N 堆石子排成一排,其编号为 1,2,3,…,N。每堆石子有一定的质量,可以用一个整数来描述,现在要将这 N 堆石子合并成为一堆。每次只能合并相邻的两

    相关 整数划分 区间dp

    题目链接[点击打开链接][Link 1] 题目大意是说有一个不超过二十位的数字,要将这个数字划分成n段,最后让这n段数字相乘,问怎么划分使乘积最大。 分析: 一

    相关 51nod1021石子归并区间dp

    题意:N堆石子摆成一条线。现要将石子有次序地合并成一堆。规定每次只能选相邻的2堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的代价。计算将N堆石子合并成一堆的最小代价。