多阶段决策问题
我们在解决动态规划问题的时候,往往不会很轻松的写出递推方程。这时候我们需要考虑一下是否需要借鉴"多阶段决策问题"。
https://www.cnblogs.com/woxiaosade/p/10346052.html
上面的这道“硬币问题”的题目,如果将每一枚硬币的数目改成1,那原先的递推方程就很难写出了,我们不知道何时拿硬币,拿哪一枚硬币。
如果我们将每一枚硬币的标号,视作一个“阶段”,每一个阶段,单独对一个硬币进行分析,就只有两种情况(拿 与 不拿),再循环访问其他硬币,问题就很好解决。
下面给出两个例子,希望可以对比之前较单一的dp问题。
单向TSP
问题描述:
给一个m行n列( m<=10 , n <= 100) 的整数矩阵,从第一列的任何位置出发,每次可以向右,右上,右下走一格,最终到达最后一列的任一行。要求经过的整数之和最小。
注意:
整个矩阵是环形的,第一行的上一行是最后一行,最后一行的上一行是第一行。多解时,请输出字典序最小的解。
题目分析:
如果我们设d [ i ] 为第 i 列到最后一列的最小和,我们按照以往的经验写出递推方程
\[d[i][j] = \mathop {\max }\limits_j (a[i][j]) + d[i + j])\]
显然这个方程是错误的,我们不确定 min{ a[ i ][ j ] } 是否能达到 d [ i + 1 ] 的那一个 a [ i + 1] [ j ]。这个时候,我们要考虑将数组 d 扩大为二维。
这时候,d [ i ][ j ] 设为 从 格子 a[ i ][ j ] 出发到达最后一列的最小整数和。这个时候我们再尝试一下写递推方程,
\[d[i][j] = \mathop {\max }\limits_{0 < = k < = 2} (a[i][j]) + d[nex{\rm{t}}\_rows[k]][j + 1])\]
#include <stdio.h> #include <iostream> #include <algorithm> #define inf 0x3f3f3f3f using namespace std; int m, n; int a[15][105]; int next[15][105]; int d[15][105]; int first = 0, ans = inf ; int dp2(int i, int j){//递归 if(j == n){ d[i][j] = a[i][j]; return a[i][j]; } if(d[i][j]) return d[i][j]; d[i][j] = inf; int rows[3] = {i, i - 1, i + 1};//行号 直行 右上 右下 if(i == 1) rows[1] = m; if(i == m) rows[2] = 1; for(int k = 0; k < 3; k++){ if(dp2(rows[k], j+1) + a[i][j] < d[i][j]){ d[i][j] = dp2(rows[k], j+1) + a[i][j]; next[i][j] = rows[k]; } } if(j == 0 && d[i][j] < ans){ ans = d[i][j]; first = i; } return d[i][j]; } void dp1(){//循环 for(int i=n;i>0;i--){//逆推列 for(int j=1;j<=m;j++){//行 if(i==n) d[j][i] = a[j][i]; else{ d[j][i] = inf; int rows[3] = {j, j - 1, j + 1};//行号 直行 右上 右下 if(j == 1) rows[1] = m; if(j == m) rows[2] = 1; sort(rows,rows+3);//保证行号字典序最小 for(int k = 0; k<3;k++){ if(d[rows[k]][i+1] + a[j][i] < d[j][i]){ d[j][i] = d[rows[k]][i+1] + a[j][i]; next[j][i] = rows[k]; } } } if(j == 0 && d[j][i] < ans){ ans = d[j][i]; first = j; } } } } int main(){ scanf("%d %d", &m, &n); for(int i=1;i<=m;i++) for(int j=1;j<=n;j++) scanf("%d", &a[i][j]); for(int i=1;i<=m;i++) dp2(i,1); //dp2(1,5); for(int i=1;i<=m;i++){ for(int j=1;j<=n;j++) cout<<d[i][j]<<" "; cout<<endl; } return 0; }
如果要输出路径,最好设一个next[ i ][ j ] 数组,来记录 格子( i , j )下一行是哪一行。
0 - 1 背包问题
问题描述:
有n种物品,每种只有一个,第 i 种 物品的体积为Vi ,重量为 Wi。选一些物品装到一个容量为C的背包,使得总体积不超C的情况下,重量尽量大。
问题分析:
模仿上题,将每一个物品看作一个“阶段” ,设d [ i ][ j ] 为第 i 到第 n 个物品,装入到容量为 j 的背包中的最大重量。
递推公式: d[ i ][ j ] = max ( d( i + 1 , j) , d( i + 1 , j - V[ i ] ) + W[ i ]) (此物品选择 不装 与 装)
附部分代码
for(int i = n; i >= 1; i--){ for(int j = 0; j <= c; j++){ d[i][j] = (i == n ? 0 : d[i+1][j]); if(j >= V[i]) d[i][j] = max(d[i][j], d[i+1][j - V[i]] + W[i]); } }
递归代码如下
int dp(int p, int w) { if (d[p][w]) return d[p][w]; if (p < n) { if (w >= g[p].w) d[p][w] = max(dfs(p + 1, w), dfs(p + 1, w - g[p].w) + g[p].v); else d[p][w] = dfs(p + 1, w); } else { if (w >= g[p].w) d[p][w] = g[p].v; else d[p][w] = 0; } return d[p][w]; }
参考文献:
《算法竞赛入门经典 (第2版)》刘佳汝 P270