何为dp:
动态规划(dynamic programming),与分治相似的一种算法。
区别:
分治问题性质: 最优子结构
dp问题性质: 最优子结构+重叠子问题
dp通常用来求解最优化问题。问题可能有很多可行解,每个解都有一个值,找出具有最优值的一个(可能有多个)解。
步骤:
1.刻画最优子结构
2.递归定义最优解
3.计算最优解的值 通常自底向上
4.必要的话构建整个最优解方案
例题1: 矩阵连乘
给定n个可以连乘的矩阵序列来计算乘积。由于矩阵乘法满足结合性,存在不同的计算次序。求解所需标量乘法最少的方案,用圆括号表示。
样例输入:
5
3 4 2 1 6 5
样例输出:
65
((1,(2,3)),(4,5))
分析:
暴力?
方案数:
P(1) = 1;
P(n) = sum{P(k)*P(n-k)}
等于卡特兰数第n-1项 指数级增长
dp思路:
问题的两个性质:
最优子结构、重叠子问题。
1.刻画最优子结构
长度为n的序列,n-1种划分,其最优解由某个划分的最优解构成。
2.构造最优解
f[i,j] = 0 , i == j;
f[i, j] = min{f[i, k] + f[k+1, j] + m[i-1]*m[k]*m[j]} i<=k<=j , i < j
3.计算最优解的值
两种实现
自底向上:
对每个(l, r), 都只需要子区间的最优解,因次可以按区间长度递增进行求解。
长度为1的区间答案为0,长度为2的区间由长度为1的求出,依次类推,直到求出(1, n)。
实现:
for (int i = 1; i <= n; i++)
buf[i][i] = 0;
for (int l = 2; l <= n; l++)
for (int i = 1; i <= n-l+1; i++) {
int j = i + l - 1;
buf[i][j] = INF;
for (int k = i; k < j; k++) {
int sum = buf[i][k] + buf[k+1][j] + m[i-1] * m[k] * m[j];
if (sum < buf[i][j]) {
buf[i][j] = sum;
path[i][j] = k;
}
}
}
自顶向下(备忘录):
分治为什么会超时?因为有大量子问题重复计算。
备忘录解决的办法就是给所有子问题状态打个表。第一次遇到子问题时,计算求解并存表,随后每次遇到这个子问题都只需查表返回就行。
实现:
int dp(int l, int r) {
if (l == r)
return 0;
if (buf[l][r])
return buf[l][r];
int ans = INF, mini;
for (int i = l; i < r; i++) {
int sum = dp(l, i) + dp(i+1, r) + m[l-1] * m[i] * m[r];
if (ans > sum) {
ans = sum;
mini = i;
}
}
path[l][r] = mini;
return buf[l][r] = ans;
}
两者对比:
通常情况下,自底向上要快一些,因为相比备忘录没有递归调用的一些;
某些不需要所有子问题的情况,备忘录要好一些。
4.构建最优解方案
有一个二维数组保存每个区间的分割点,递归即可。
void print(int l, int r) {
if (l