直线石子合并(区间DP)
Posted chr1stopher
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了直线石子合并(区间DP)相关的知识,希望对你有一定的参考价值。
石子合并
时间限制:1000 ms | 内存限制:65535 KB
描述
有N堆石子排成一排,每堆石子有一定的数量。现要将N堆石子并成为一堆。合并的过程只能每次将相邻的两堆石子堆成一堆,每次合并花费的代价为这两堆石子的和,经过N-1次合并后成为一堆。求出总的代价最小值和最大值。
输入
有多组测试数据,输入到文件结束。
每组测试数据第一行有一个整数n,表示有n堆石子。
接下来的一行有n(0< n <200)个数,分别表示这n堆石子的数目,用空格隔开
输出
输出总代价的最小值以及最大值(中间以空格隔开),占单独的一行
样例输入
3
1 2 3
7
13 7 8 16 21 4 18
样例输出
9 11
239 365
思路:
该题为区间DP好题,这里简单谈一谈区间DP :
区间DP,就是在某一区间内满足某个性质,比如最简单的最大最小,一般区间dp有明显的区间性,区别一些线性DP,线性DP每个状态都由前一个转移而来,区间dp也是,但是是由前面区间转移而来,区间dp一般问的是某个区间的某个性质,区间dp从区间是1,是2,是3一步一步转化过来,区间为2就是两个区间为1相加,这样所有区间为2的都就转移出来,如果区间为4的,可能是区间1和区间3,也可能是区间2和区间2,因为区间1区间2区间3所有情况都枚举过,所以直接枚举转移就好,简单的区间dp代码有很强的套路性。(看完可能不认识“区”这个字了= =)
区间动规一般都是三层for循环, 前两层用来控制区间长度, 最后一层用来枚举区间内最后一次的位置, 还有需要注意的是区间要从小到大, 因为动态规划就是后面得用到前面得出的结果递推后面的结果。
状态转移方程
dp[i][j] = min(dp[i][j],dp[i][k]+dp[k+1][j]+sum[i][j]);
/
dp[i][j] = max(dp[i][j],dp[i][k]+dp[k+1][j]+sum[i][j]);
AC code:
#include<stdio.h> #include<iostream> #include<string.h> #include<algorithm> #include<math.h> #include<string> #include<queue> #include<utility> using namespace std; typedef long long ll; const int MX = 1e2+7; const int INF = 0x3f3f3f3f; int dp1[MX][MX],dp2[MX][MX],sum[MX][MX]; int n,a[MX]; int main(int argc, char const *argv[]) { while(~scanf("%d",&n)) { //一定要记得初始化 memset(dp1,0,sizeof(dp1)); memset(dp2,0,sizeof(dp2)); memset(sum,0,sizeof(sum)); for(int i = 1;i <= n;i++) { scanf("%d",&a[i]); sum[i][i] = a[i]; } for(int i = 1;i <= n;i++) for(int j = i;j <= n;j++) dp1[i][j] = i == j ? 0 : INF;//dp[i][i]只有一个数字无法合并,代价为0 for(int len = 1;len < n;len++)//枚举区间长度 { for(int i = 1;i + len <= n;i++)//枚举区间起点 { int j = i + len;//枚举区间终点 for(int k = i;k < j;k++)//枚举区间断点 { sum[i][j] = sum[i][k]+sum[k+1][j];//sum[i][j]是用来储存i~j石子总数,一般写法是用前缀和计算,这里同样采用动态规划 dp1[i][j] = min(dp1[i][j],dp1[i][k]+dp1[k+1][j]+sum[i][j]); dp2[i][j] = max(dp2[i][j],dp2[i][k]+dp2[k+1][j]+sum[i][j]); } } } printf("%d %d ",dp1[1][n],dp2[1][n]); } return 0; }
以上是关于直线石子合并(区间DP)的主要内容,如果未能解决你的问题,请参考以下文章