今天学长给我们讲了区间dp,当然听得云里雾里,讲完之后基本处于自闭状态,然后还是自己到大佬的博客,然后看博客,但是并没有找到很详细的博客,所以我想自己写一写,大神们勿喷哈.
一 .定义
区间DP,顾名思义是在区间上DP,它的主要思想就是先在小区间进行DP得到最优解,然后再利用小区间的最优解合并求大区间的最优解。
一般代码格式
//一般区间DP实现代码
规定 dp[i][j] 为区间[i,j] 上的最优解
for (int i = 1; i <= n; i++) //区间长度为1的初始化
dp[i][i] = x; // x视情况而定
for (int len = 2; len <= n; len++) //枚举区间长度
{
for (int i = 1, j = len; j <= n; i++, j++) //区间[i,j]
{
//DP方程实现
}
}
1. 石子合并
有N堆石子,现要将石子有序的合并成一堆,规定如下:每次只能移动相邻的2堆石子合并,合并花费为新合成的一堆石子的数量。求将这N堆石子合并成一堆的总花费最小(或最大)。
引入区间dp
区间dp的思想是 利用一次次的合并过程去更新所得的结果,以便下一次合并使用。
从小的区间一点一点分析
设 A[i] 为第i堆的石子数目
1.若有一堆石子,无需合并 花费为 0
2.若有两堆石子,将这两堆合并 花费为A[0] + A[1]
3.若有三堆石子,有两种情况
- 先合并A[0],A[1] 再与A[2] 合并
- 先合并A[1] A[2] 再与A[0] 合并
4.若有四堆石子,有三种情况 ……
分析一下算法过程
dp 二维数组 ,横坐标代表区间起点,纵坐标代表区间终点 . 比如: dp[1][2] 代表 第一堆石子到第二堆石子的最优解,下面详细说吧,
可能这样提前说不太理解.
首先 最外层循环 控制石子堆的长度(len = 2 to n)
for(int len = 2;len<=n;++len)
第二层循环,确定区间起点与终点
for(int i=1,j=len;j<=n;++i,++j)
经过分析还需要一层循环,枚举将区间分割的那个点k
for(int k = i;k<j;++k)
最后来看这个动态转移方程
dp[i][j] : 区间[i,j]上的石子合并最小花费
sum[i][j]: 区间[i,j] 的和
dp[i][j] = min(dp[i][j],dp[i][k]+dp[k+1][j] + sum[i][j]);
动态转移方程可以这么理解 现在已经将第 i 堆到第k堆 、 第k+1堆到第j堆合并了,现在只剩下这两堆 要将这两堆去合并
那么就是利用之前算出来的dp[i][k] + dp[k+1][j] 再加上区间[i,j]的和
为什么是加区间[i,j]的和呢 你把[i,k] 和[k+1,j]的石子合并到了一起
合并这两个区间的花费一定是这两个区间的总石子数,当然也就是[i,j]总石子数。
过程 :
1 初始化 dp数组
过程 :
整个过程就这样的 ,不断的更新小区间最优解,然后最总就可以得到大区间的最优解.
链接 : https://vjudge.net/problem/51Nod-1021
代码 :
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <queue>
#define d(x) for(int i = 1 ;i<=(x) ; i++)
#define Init_(x) memset(x,0,sizeof(x))
#define pi acos(-1)
#define while_(x) while(scanf("%d",&x)!=EOF)
#define while_c(x) while(x--)
#define scanf_(x,y) scanf("%d%d",&x,&y)
#define mst(a,b) memset((a),(b),sizeof(a))
using namespace std ;
const int inf = 1<<30 ;
typedef long long LL ;
const LL Mod = 1e9+7 ;
int dp[1005][1005] ;
int sum[1005] ;
int main()
{
// 区间 dp
int n ;
cin >> n ;
sum[0] = 0 ;
memset(dp,0x3f3f,sizeof(dp));
for(int i = 1 ;i <=n ; i++ )
{
int x ;
cin >>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 = len-1+i ; // 区间终点
if(j>n)
continue; // yuejie
for(int k = 1 ; k<j ; k++) // 分割点
{
dp[i][j] = min(dp[i][j],dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]) ;
}
}
}
//
// for(int i = 1 ; i<=n ; i++)
// {
// for(int j = 1 ; j<=n; j++)
// {
// cout<<dp[i][j]<<" ";
// }
// cout<<endl;
// }
cout<<dp[1][n] <<endl;
return 0 ;
}
今天先写到这里,等学了其他的再加补充.