赞
踩
区间DP,顾名思义是在区间上DP,它的主要思想就是先在小区间进行DP得到最优解,然后再利用小区间的最优解合并求大区间的最优解。
区间动态规划问题一般都是考虑,对于每段区间,他们的最优值都是由几段更小区间的最优值得到,是分治思想的一种应用,将一个区间问题不断划分为更小的区间直至一个元素组成的区间,枚举他们的组合 ,求合并后的最优值。
设F[i,j](1<=i<=j<=n)表示区间[i,j]内的数字相加的最小代价
最小区间F[i,i]=0(一个数字无法合并,∴代价为0)
每次用变量k(i<=k<=j-1)将区间分为[i,k]和[k+1,j]两段
For l:=1 to n do // l是区间长度,作为阶段。
for i:=1 to n do // i是穷举的区间的起点
begin
j:=i+l-1; // j是 区间的终点,这样所有的区间就穷举完毕
if j>n then break; // 这个if很关键。
for k:= i to j-1 do // 状态转移,去推出 f[i,j]
f[i , j]= max{f[ i,k]+ f[k+1,j]+ w[i,j] }
end;
这个结构必须记好,这是区间动态规划的代码结构。
伪代码:
- //mst(dp,0) 初始化DP数组
- for(int i=1;i<=n;i++)
- {
- dp[i][i]=初始值
- }
- for(int len=2;len<=n;len++) //区间长度
- for(int i=1;i<=n;i++) //枚举起点
- {
- int j=i+len-1; //区间终点
- if(j>n) break; //越界结束
- for(int k=i;k<j;k++) //枚举分割点,构造状态转移方程
- {
- dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]+w[i][j]);
- }
- }
描述
有N堆石子排成一排,每堆石子有一定的数量。现要将N堆石子并成为一堆。合并的过程只能每次将相邻的两堆石子堆成一堆,每次合并花费的代价为这两堆石子的和,经过N-1次合并后成为一堆。求出总的代价最小值。
输入
有多组测试数据,输入到文件结束。
每组测试数据第一行有一个整数n,表示有n堆石子。
接下来的一行有n(0< n <200)个数,分别表示这n堆石子的数目,用空格隔开
输出
输出总代价的最小值,占单独的一行
样例输入
3
1 2 3
7
13 7 8 16 21 4 18
样例输出
9
239
要求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).
代码如下:
-
- #include <cstdio>
- #include <queue>
- #include <cstring>
- #include <algorithm>
- using namespace std;
- #define mst(a,b) memset((a),(b),sizeof(a))
- #define rush() int T;scanf("%d",&T);while(T--)
-
- typedef long long ll;
- const int maxn = 205;
- const ll mod = 1e9+7;
- const ll INF = 1e18;
- const double eps = 1e-9;
-
- int n,x;
- int sum[maxn];
- int dp[maxn][maxn];
-
- int main()
- {
- while(~scanf("%d",&n))
- {
- sum[0]=0;
- mst(dp,0x3f);
- for(int i=1;i<=n;i++)
- {
- scanf("%d",&x);
- sum[i]=sum[i-1]+x;
- dp[i][i]=0;
- }
- for(int len=2;len<=n;len++)
- for(int i=1;i<=n;i++)
- {
- int j=i+len-1;
- if(j>n) continue;
- for(int k=i;k<j;k++)
- {
- dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]);
- }
- }
- printf("%d\n",dp[1][n]);
- }
- return 0;
- }
【平行四边形优化】
上面的代码运行时间在240ms左右,通过这题完全没问题,但我们还可以考虑优化。
由于状态转移时是三重循环的,我们想能否把其中一层优化呢?尤其是枚举分割点的那个,显然我们用了大量的时间去寻找这个最优分割点,所以我们考虑把这个点找到后保存下来
用s[i][j]表示区间[i,j]中的最优分割点,那么第三重循环可以从[i,j-1)优化到【s[i][j-1],s[i+1][j]】。(这个时候小区间s[i][j-1]和s[i+1][j]的值已经求出来了,然后通过这个循环又可以得到s[i][j]的值)。
代码如下:
-
- #include <cstdio>
- #include <queue>
- #include <cstring>
- #include <algorithm>
- using namespace std;
- #define mst(a,b) memset((a),(b),sizeof(a))
- #define rush() int T;scanf("%d",&T);while(T--)
-
- typedef long long ll;
- const int maxn = 205;
- const ll mod = 1e9+7;
- const ll INF = 1e18;
- const double eps = 1e-9;
-
- int n,x;
- int sum[maxn];
- int dp[maxn][maxn];
- int s[maxn][maxn];
-
- int main()
- {
- while(~scanf("%d",&n))
- {
- sum[0]=0;
- mst(dp,0x3f);
- for(int i=1;i<=n;i++)
- {
- scanf("%d",&x);
- sum[i]=sum[i-1]+x;
- dp[i][i]=0;
- s[i][i]=i;
- }
- for(int len=2;len<=n;len++)
- for(int i=1;i<=n;i++)
- {
- int j=i+len-1;
- if(j>n) continue;
- for(int k=s[i][j-1];k<=s[i+1][j];k++)
- {
- if(dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]<dp[i][j])
- {
- dp[i][j]=dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1];
- s[i][j]=k;
- }
- }
- }
- printf("%d\n",dp[1][n]);
- }
- return 0;
- }
时间【32ms】
以下是一些参考资料
石子归并,括号匹配,整数划分
https://blog.csdn.net/y990041769/article/details/24194605
石子合并(三) 环形合并
https://blog.csdn.net/qq_36183935/article/details/70808356
https://www.cnblogs.com/hadilo/p/5800306.html
动态规划加速原理之四边形不等式
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。