多阶段决策问题

我们在解决动态规划问题的时候,往往不会很轻松的写出递推方程。这时候我们需要考虑一下是否需要借鉴"多阶段决策问题"。

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;
}
View Code

 

  如果要输出路径,最好设一个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

posted @ 2019-02-03 16:42  阳离子  阅读(1039)  评论(0编辑  收藏  举报